Added path and urlBuilder to gsp dsl.

This commit is contained in:
Jesse Brault 2023-02-14 13:19:28 +01:00
parent 3b8cf46260
commit 0371d41ccc
15 changed files with 271 additions and 96 deletions

View File

@ -20,13 +20,16 @@ class EmbeddablePart {
private final Collection<Part> allParts
private final String path
String render(Map binding = [:]) {
def result = part.type.renderer.render(
this.part,
binding,
this.globals,
this.text,
this.allParts
this.allParts,
this.path
)
if (result.v1.size() > 0) {
this.onDiagnostics.call(result.v1)

View File

@ -14,13 +14,14 @@ class EmbeddablePartsMap {
Collection<Part> parts,
Map globals,
Closure onDiagnostics,
@Nullable EmbeddableText text = null
@Nullable EmbeddableText text = null,
String path
) {
Objects.requireNonNull(parts)
Objects.requireNonNull(globals)
Objects.requireNonNull(onDiagnostics)
parts.each {
this.put(it.path, new EmbeddablePart(it, globals, onDiagnostics, text, parts))
this.put(it.path, new EmbeddablePart(it, globals, onDiagnostics, text, parts, path))
}
}

View File

@ -3,6 +3,7 @@ package com.jessebrault.ssg.part
import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.tagbuilder.DynamicTagBuilder
import com.jessebrault.ssg.text.EmbeddableText
import com.jessebrault.ssg.url.PathBasedUrlBuilder
import groovy.text.GStringTemplateEngine
import groovy.text.TemplateEngine
import groovy.transform.EqualsAndHashCode
@ -19,20 +20,24 @@ class GspPartRenderer implements PartRenderer {
Map binding,
Map globals,
@Nullable EmbeddableText text = null,
Collection<Part> allParts
Collection<Part> allParts,
String path
) {
Objects.requireNonNull(part)
Objects.requireNonNull(binding)
Objects.requireNonNull(globals)
Objects.requireNonNull(allParts)
Objects.requireNonNull(path)
def embeddedPartDiagnostics = []
try {
def result = engine.createTemplate(part.text).make([
binding: binding,
globals: globals,
parts: new EmbeddablePartsMap(allParts, globals, embeddedPartDiagnostics.&addAll, text),
parts: new EmbeddablePartsMap(allParts, globals, embeddedPartDiagnostics.&addAll, text, path),
path: path,
tagBuilder: new DynamicTagBuilder(),
text: text
text: text,
urlBuilder: new PathBasedUrlBuilder(path)
])
new Tuple2<>([], result.toString())
} catch (Exception e) {

View File

@ -11,7 +11,8 @@ interface PartRenderer {
Map binding,
Map globals,
@Nullable EmbeddableText text,
Collection<Part> allParts
Collection<Part> allParts,
String path
)
}

View File

@ -6,6 +6,7 @@ import com.jessebrault.ssg.part.EmbeddablePartsMap
import com.jessebrault.ssg.tagbuilder.DynamicTagBuilder
import com.jessebrault.ssg.text.EmbeddableTextsCollection
import com.jessebrault.ssg.text.Text
import com.jessebrault.ssg.url.PathBasedUrlBuilder
import groovy.text.GStringTemplateEngine
import groovy.text.TemplateEngine
import groovy.transform.EqualsAndHashCode
@ -18,18 +19,23 @@ class GspSpecialPageRenderer implements SpecialPageRenderer {
private static final TemplateEngine engine = new GStringTemplateEngine()
@Override
Tuple2<Collection<Diagnostic>, String> render(SpecialPage specialPage, Collection<Text> texts, Collection<Part> parts, Map globals) {
Tuple2<Collection<Diagnostic>, String> render(
SpecialPage specialPage,
Collection<Text> texts,
Collection<Part> parts,
Map globals
) {
try {
Collection<Diagnostic> diagnostics = []
def result = engine.createTemplate(specialPage.text).make([
globals: globals,
parts: new EmbeddablePartsMap(parts, globals, { Collection<Diagnostic> partDiagnostics ->
diagnostics.addAll(partDiagnostics)
}),
parts: new EmbeddablePartsMap(parts, globals, diagnostics.&addAll, specialPage.path),
path: specialPage.path,
tagBuilder: new DynamicTagBuilder(),
texts: new EmbeddableTextsCollection(texts, globals, { Collection<Diagnostic> textDiagnostics ->
diagnostics.addAll(textDiagnostics)
})
}),
urlBuilder: new PathBasedUrlBuilder(specialPage.path)
])
new Tuple2<>(diagnostics, result.toString())
} catch (Exception e) {

View File

@ -7,6 +7,7 @@ import com.jessebrault.ssg.tagbuilder.DynamicTagBuilder
import com.jessebrault.ssg.text.EmbeddableText
import com.jessebrault.ssg.text.FrontMatter
import com.jessebrault.ssg.text.Text
import com.jessebrault.ssg.url.PathBasedUrlBuilder
import groovy.text.GStringTemplateEngine
import groovy.text.TemplateEngine
import groovy.transform.EqualsAndHashCode
@ -36,9 +37,11 @@ class GspTemplateRenderer implements TemplateRenderer {
def result = engine.createTemplate(template.text).make([
frontMatter: frontMatter,
globals: globals,
parts: new EmbeddablePartsMap(parts, globals, onDiagnostics, embeddableText),
parts: new EmbeddablePartsMap(parts, globals, onDiagnostics, embeddableText, text.path),
path: text.path,
tagBuilder: new DynamicTagBuilder(),
text: embeddableText
text: embeddableText,
urlBuilder: new PathBasedUrlBuilder(text.path)
])
new Tuple2<>(diagnostics, result.toString())
} catch (Exception e) {

View File

@ -0,0 +1,23 @@
package com.jessebrault.ssg.url
import java.nio.file.Path
class PathBasedUrlBuilder implements UrlBuilder {
private final Path fromDirectory
PathBasedUrlBuilder(String fromFile) {
def fromFilePath = Path.of(fromFile)
if (fromFilePath.parent) {
this.fromDirectory = fromFilePath.parent
} else {
this.fromDirectory = Path.of('')
}
}
@Override
String relative(String to) {
this.fromDirectory.relativize(Path.of(to)).toString()
}
}

View File

@ -0,0 +1,5 @@
package com.jessebrault.ssg.url
interface UrlBuilder {
String relative(String to)
}

View File

@ -1,35 +1,22 @@
package com.jessebrault.ssg.part
import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.text.*
import com.jessebrault.ssg.text.EmbeddableText
import org.junit.jupiter.api.Test
import static com.jessebrault.ssg.testutil.DiagnosticsUtil.assertEmptyDiagnostics
import static com.jessebrault.ssg.testutil.DiagnosticsUtil.getDiagnosticsMessageSupplier
import static com.jessebrault.ssg.text.TextMocks.renderableText
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, [:], [:], null, [part])
def r = this.renderer.render(part, [:], [:], null, [part], '')
assertTrue(r.v1.size() == 0)
assertEquals('Hello, World!', r.v2)
}
@ -42,7 +29,8 @@ class GspPartRendererTests {
[greeting: 'Hello, World!'],
[:],
null,
[part]
[part],
''
)
assertTrue(r.v1.size() == 0)
assertEquals('Hello, World!', r.v2)
@ -56,7 +44,8 @@ class GspPartRendererTests {
[:],
[greeting: 'Hello, World!'],
null,
[part]
[part],
''
)
assertTrue(r.v1.size() == 0)
assertEquals('Hello, World!', r.v2)
@ -70,12 +59,9 @@ class GspPartRendererTests {
part,
[:],
[:],
new EmbeddableText(
mockRenderableText('Hello, World!'),
[:],
{ Collection<Diagnostic> diagnostics -> textDiagnostics.addAll(diagnostics) }
),
[part]
new EmbeddableText(renderableText('Hello, World!'), [:], textDiagnostics.&addAll),
[part],
''
)
assertTrue(textDiagnostics.isEmpty())
assertTrue(r.v1.isEmpty())
@ -85,7 +71,7 @@ class GspPartRendererTests {
@Test
void tagBuilderAvailable() {
def part = new Part('', null, '<%= tagBuilder.test() %>')
def r = this.renderer.render(part, [:], [:], null, [part])
def r = this.renderer.render(part, [:], [:], null, [part], '')
assertTrue(r.v1.isEmpty())
assertEquals('<test />', r.v2)
}
@ -95,9 +81,39 @@ class GspPartRendererTests {
def partType = new PartType(['.gsp'], this.renderer)
def part0 = new Part('part0.gsp', partType, '<%= parts["part1.gsp"].render() %>')
def part1 = new Part('part1.gsp', partType, 'Hello, World!')
def r = this.renderer.render(part0, [:], [:], null, [part0, part1])
def r = this.renderer.render(part0, [:], [:], null, [part0, part1], '')
assertTrue(r.v1.isEmpty(), getDiagnosticsMessageSupplier(r.v1))
assertEquals('Hello, World!', r.v2)
}
@Test
void pathAvailableIfPresent() {
def part = new Part('', null, '<%= path %>')
def r = this.renderer.render(
part,
[:],
[:],
null,
[part],
'test.md'
)
assertEmptyDiagnostics(r)
assertEquals('test.md', r.v2)
}
@Test
void urlBuilderAvailable() {
def part = new Part('', null, '<%= urlBuilder.relative("images/test.jpg") %>')
def r = this.renderer.render(
part,
[:],
[:],
null,
[part],
'test.md'
)
assertEmptyDiagnostics(r)
assertEquals('images/test.jpg', r.v2)
}
}

View File

@ -9,8 +9,8 @@ import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.Mock
import org.mockito.junit.jupiter.MockitoExtension
import static com.jessebrault.ssg.testutil.DiagnosticsUtil.assertEmptyDiagnostics
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.when
@ -22,39 +22,41 @@ class GspSpecialPageRendererTests {
@Test
void rendersGlobal() {
def specialPage = new SpecialPage("<%= globals['greeting'] %>", null, null)
def specialPage = new SpecialPage("<%= globals['greeting'] %>", 'test.gsp', null)
def globals = [greeting: 'Hello, World!']
def r = this.renderer.render(specialPage, [], [], globals)
assertTrue(r.v1.size() == 0)
assertEmptyDiagnostics(r)
assertEquals('Hello, World!', r.v2)
}
@Test
void rendersPartWithNoBinding(@Mock PartRenderer partRenderer) {
when(partRenderer.render(any(), any(), any(), any())).thenReturn(new Tuple2<>([], 'Hello, World!'))
when(partRenderer.render(any(), any(), any(), any(), 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() %>", null, null)
def specialPage = new SpecialPage("<%= parts['test'].render() %>", 'test.gsp', null)
def r = this.renderer.render(specialPage, [], [part], [:])
assertTrue(r.v1.size() == 0)
assertEmptyDiagnostics(r)
assertEquals('Hello, World!', r.v2)
}
@Test
void rendersPartWithBinding(@Mock PartRenderer partRenderer) {
when(partRenderer.render(any(), argThat { Map m -> m.get('greeting') == 'Hello, World!'}, any(), any()))
.thenReturn(new Tuple2<>([], 'Hello, World!'))
when(partRenderer.render(
any(), argThat { Map m -> m.get('greeting') == 'Hello, World!'}, any(), any(), 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,
'test.gsp',
null
)
def r = this.renderer.render(specialPage, [], [part], [:])
assertTrue(r.v1.size() == 0)
assertEmptyDiagnostics(r)
assertEquals('Hello, World!', r.v2)
}
@ -67,20 +69,40 @@ class GspSpecialPageRendererTests {
def specialPage = new SpecialPage(
"<%= texts.find { it.path == 'test' }.render() %>",
null,
'test.gsp',
null
)
def r = this.renderer.render(specialPage, [text], [], [:])
assertTrue(r.v1.size() == 0)
assertEmptyDiagnostics(r)
assertEquals('<p><strong>Hello, World!</strong></p>\n', r.v2)
}
@Test
void tagBuilderAvailable() {
def specialPage = new SpecialPage('<%= tagBuilder.test() %>', null, null)
def specialPage = new SpecialPage('<%= tagBuilder.test() %>', 'test.gsp', null)
def r = this.renderer.render(specialPage, [], [], [:])
assertTrue(r.v1.isEmpty())
assertEmptyDiagnostics(r)
assertEquals('<test />', r.v2)
}
@Test
void pathAvailable() {
def specialPage = new SpecialPage('<%= path %>', 'test.gsp', null)
def r = this.renderer.render(specialPage, [], [], [:])
assertEmptyDiagnostics(r)
assertEquals('test.gsp', r.v2)
}
@Test
void urlBuilderAvailable() {
def specialPage = new SpecialPage(
'<%= urlBuilder.relative("images/test.jpg") %>',
'test.gsp',
null
)
def r = this.renderer.render(specialPage, [], [], [:])
assertEmptyDiagnostics(r)
assertEquals('images/test.jpg', r.v2)
}
}

View File

@ -3,48 +3,23 @@ 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
import org.mockito.junit.jupiter.MockitoExtension
import static com.jessebrault.ssg.testutil.DiagnosticsUtil.getDiagnosticsMessageSupplier
import static com.jessebrault.ssg.text.TextMocks.*
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
@ -55,18 +30,19 @@ class GspTemplateRendererTests {
null
)
when(partRenderer.render(any(), any(), any(), any())).thenReturn(new Tuple2<>([], 'Hello, World!'))
when(partRenderer.render(any(), any(), 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, [:]),
mockBlankText(),
blankText(),
[part],
[:]
)
assertTrue(r.v1.size() == 0)
assertTrue(r.v1.isEmpty(), getDiagnosticsMessageSupplier(r.v1))
assertEquals('Hello, World!', r.v2)
}
@ -78,7 +54,7 @@ class GspTemplateRendererTests {
null
)
when(partRenderer.render(any(), argThat { Map m -> m.get('person') == 'World' }, any(), any()))
when(partRenderer.render(any(), argThat { Map m -> m.get('person') == 'World' }, any(), any(), any(), any()))
.thenReturn(new Tuple2<>([], 'Hello, World!'))
def partType = new PartType([], partRenderer)
def part = new Part('greeting', partType, null)
@ -86,11 +62,11 @@ class GspTemplateRendererTests {
def r = this.renderer.render(
template,
new FrontMatter(null, [:]),
mockBlankText(),
blankText(),
[part],
[:]
)
assertTrue(r.v1.size() == 0)
assertTrue(r.v1.isEmpty(), getDiagnosticsMessageSupplier(r.v1))
assertEquals('Hello, World!', r.v2)
}
@ -100,11 +76,11 @@ class GspTemplateRendererTests {
def r = this.renderer.render(
template,
new FrontMatter(null, [title: ['Hello!']]),
mockBlankText(),
blankText(),
[],
[:]
)
assertTrue(r.v1.size() == 0)
assertTrue(r.v1.isEmpty(), getDiagnosticsMessageSupplier(r.v1))
assertEquals('Hello!', r.v2)
}
@ -114,11 +90,11 @@ class GspTemplateRendererTests {
def r = this.renderer.render(
template,
new FrontMatter(null, [:]),
mockBlankText(),
blankText(),
[],
[test: 'Hello, World!']
)
assertTrue(r.v1.size() == 0)
assertTrue(r.v1.isEmpty(), getDiagnosticsMessageSupplier(r.v1))
assertEquals('Hello, World!', r.v2)
}
@ -128,11 +104,11 @@ class GspTemplateRendererTests {
def r = this.renderer.render(
template,
new FrontMatter(null, [:]),
mockRenderableText('Hello, World!'),
renderableText('Hello, World!'),
[],
[:]
)
assertTrue(r.v1.size() == 0)
assertTrue(r.v1.isEmpty(), getDiagnosticsMessageSupplier(r.v1))
assertEquals('Hello, World!', r.v2)
}
@ -142,12 +118,40 @@ class GspTemplateRendererTests {
def r = this.renderer.render(
template,
new FrontMatter(null, [:]),
mockBlankText(),
blankText(),
[],
[:]
)
assertTrue(r.v1.isEmpty())
assertTrue(r.v1.isEmpty(), getDiagnosticsMessageSupplier(r.v1))
assertEquals('<test />', r.v2)
}
@Test
void pathAvailable() {
def template = new Template('<%= path %>', null, null)
def r = this.renderer.render(
template,
new FrontMatter(null, [:]),
textWithPath('test.md'),
[],
[:]
)
assertTrue(r.v1.isEmpty(), getDiagnosticsMessageSupplier(r.v1))
assertEquals('test.md', r.v2)
}
@Test
void urlBuilderAvailable() {
def template = new Template('<%= urlBuilder.relative("images/test.jpg") %>', null, null)
def r = this.renderer.render(
template,
new FrontMatter(null, [:]),
textWithPath('test.md'),
[],
[:]
)
assertTrue(r.v1.isEmpty(), getDiagnosticsMessageSupplier(r.v1))
assertEquals('images/test.jpg', r.v2)
}
}

View File

@ -0,0 +1,10 @@
package com.jessebrault.ssg.url
class PathBasedUrlBuilderTests extends AbstractUrlBuilderTests {
@Override
protected UrlBuilder getUrlBuilder(String fromFile) {
new PathBasedUrlBuilder(fromFile)
}
}

View File

@ -2,6 +2,8 @@ package com.jessebrault.ssg.testutil
import com.jessebrault.ssg.Diagnostic
import static org.junit.jupiter.api.Assertions.assertTrue
class DiagnosticsUtil {
static Closure<String> getDiagnosticsMessageSupplier(Collection<Diagnostic> diagnostics) {
@ -15,4 +17,8 @@ class DiagnosticsUtil {
}
}
static void assertEmptyDiagnostics(Tuple2<Collection<Diagnostic>, ?> result) {
assertTrue(result.v1.isEmpty(), getDiagnosticsMessageSupplier(result.v1))
}
}

View File

@ -0,0 +1,37 @@
package com.jessebrault.ssg.text
import com.jessebrault.ssg.text.ExcerptGetter
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 static org.mockito.ArgumentMatchers.any
import static org.mockito.Mockito.mock
import static org.mockito.Mockito.when
class TextMocks {
static Text blankText() {
def textRenderer = mock(TextRenderer)
def frontMatterGetter = mock(FrontMatterGetter)
def excerptGetter = mock(ExcerptGetter)
new Text('', '', new TextType([], textRenderer, frontMatterGetter, excerptGetter))
}
static Text renderableText(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))
}
static Text textWithPath(String path) {
def textRenderer = mock(TextRenderer)
def frontMatterGetter = mock(FrontMatterGetter)
def excerptGetter = mock(ExcerptGetter)
new Text('', path, new TextType([], textRenderer, frontMatterGetter, excerptGetter))
}
}

View File

@ -0,0 +1,33 @@
package com.jessebrault.ssg.url
import org.junit.jupiter.api.Test
import static org.junit.jupiter.api.Assertions.assertEquals
abstract class AbstractUrlBuilderTests {
protected abstract UrlBuilder getUrlBuilder(String fromFile);
@Test
void upDownDown() {
def builder = this.getUrlBuilder('posts/post.html')
assertEquals('../images/test.jpg', builder.relative('images/test.jpg'))
}
@Test
void downDown() {
assertEquals(
'images/test.jpg',
this.getUrlBuilder('test.html').relative('images/test.jpg')
)
}
@Test
void upUpDownDown() {
assertEquals(
'../../images/test.jpg',
this.getUrlBuilder('posts/old/test.html').relative('images/test.jpg')
)
}
}