From 34d9cd52b0926261320a5a2ea8dbf8bf99b8806c Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Sat, 11 Feb 2023 11:24:14 +0100 Subject: [PATCH 01/36] Templates/Parts now have access to an EmbeddableText object. --- buildSrc/src/main/groovy/ssg.lib.gradle | 3 + .../ssg/SimpleStaticSiteGenerator.groovy | 21 ++--- .../ssg/part/EmbeddablePart.groovy | 12 ++- .../ssg/part/EmbeddablePartsMap.groovy | 16 +++- .../ssg/part/GspPartRenderer.groovy | 20 ++++- .../jessebrault/ssg/part/PartRenderer.groovy | 11 ++- .../ssg/template/GspTemplateRenderer.groovy | 14 ++-- .../ssg/template/TemplateRenderer.groovy | 6 +- ...StaticSiteGeneratorIntegrationTests.groovy | 4 +- .../ssg/part/GspPartRendererTests.groovy | 41 +++++++++- .../GspSpecialPageRendererTests.groovy | 26 ++++--- .../template/GspTemplateRendererTests.groovy | 76 ++++++++++++++++--- 12 files changed, 197 insertions(+), 53 deletions(-) diff --git a/buildSrc/src/main/groovy/ssg.lib.gradle b/buildSrc/src/main/groovy/ssg.lib.gradle index 443abeb..78f61fa 100644 --- a/buildSrc/src/main/groovy/ssg.lib.gradle +++ b/buildSrc/src/main/groovy/ssg.lib.gradle @@ -7,6 +7,9 @@ repositories { } dependencies { + // https://mvnrepository.com/artifact/org.jetbrains/annotations + api 'org.jetbrains:annotations:24.0.0' + /** * Logging */ diff --git a/lib/src/main/groovy/com/jessebrault/ssg/SimpleStaticSiteGenerator.groovy b/lib/src/main/groovy/com/jessebrault/ssg/SimpleStaticSiteGenerator.groovy index f2dd9cf..04504ed 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/SimpleStaticSiteGenerator.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/SimpleStaticSiteGenerator.groovy @@ -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) diff --git a/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePart.groovy b/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePart.groovy index 9e7d1ab..ceeb31b 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePart.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePart.groovy @@ -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) '' diff --git a/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePartsMap.groovy b/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePartsMap.groovy index 0db363b..5473bdc 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePartsMap.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePartsMap.groovy @@ -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 partsMap = [:] - EmbeddablePartsMap(Collection parts, Map globals, Closure onDiagnostics) { + EmbeddablePartsMap( + Collection 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)) } } diff --git a/lib/src/main/groovy/com/jessebrault/ssg/part/GspPartRenderer.groovy b/lib/src/main/groovy/com/jessebrault/ssg/part/GspPartRenderer.groovy index c1f783e..84e7812 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/part/GspPartRenderer.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/part/GspPartRenderer.groovy @@ -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, String> render(Part part, Map binding, Map globals) { + Tuple2, 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)], + '' + ) } } diff --git a/lib/src/main/groovy/com/jessebrault/ssg/part/PartRenderer.groovy b/lib/src/main/groovy/com/jessebrault/ssg/part/PartRenderer.groovy index 7675fdd..2995b19 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/part/PartRenderer.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/part/PartRenderer.groovy @@ -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, String> render(Part part, Map binding, Map globals) + + Tuple2, String> render( + Part part, + Map binding, + Map globals, + @Nullable EmbeddableText text + ) + } diff --git a/lib/src/main/groovy/com/jessebrault/ssg/template/GspTemplateRenderer.groovy b/lib/src/main/groovy/com/jessebrault/ssg/template/GspTemplateRenderer.groovy index ec538b6..fc91f9d 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/template/GspTemplateRenderer.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/template/GspTemplateRenderer.groovy @@ -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, String> render( Template template, FrontMatter frontMatter, - String text, + Text text, Collection parts, Map globals ) { try { Collection diagnostics = [] + def onDiagnostics = { Collection 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 partDiagnostics -> - diagnostics.addAll(partDiagnostics) - }), - text: text + parts: new EmbeddablePartsMap(parts, globals, onDiagnostics, embeddableText), + text: embeddableText ]) new Tuple2<>(diagnostics, result.toString()) } catch (Exception e) { diff --git a/lib/src/main/groovy/com/jessebrault/ssg/template/TemplateRenderer.groovy b/lib/src/main/groovy/com/jessebrault/ssg/template/TemplateRenderer.groovy index 71e2b5a..62d2021 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/template/TemplateRenderer.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/template/TemplateRenderer.groovy @@ -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, String> render( Template template, FrontMatter frontMatter, - String text, + Text text, Collection parts, Map globals ) diff --git a/lib/src/test/groovy/com/jessebrault/ssg/SimpleStaticSiteGeneratorIntegrationTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/SimpleStaticSiteGeneratorIntegrationTests.groovy index dee6973..4ca52bc 100644 --- a/lib/src/test/groovy/com/jessebrault/ssg/SimpleStaticSiteGeneratorIntegrationTests.groovy +++ b/lib/src/test/groovy/com/jessebrault/ssg/SimpleStaticSiteGeneratorIntegrationTests.groovy @@ -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) diff --git a/lib/src/test/groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy index d905341..724b329 100644 --- a/lib/src/test/groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy +++ b/lib/src/test/groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy @@ -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 diagnostics -> textDiagnostics.addAll(diagnostics) } + ) + ) + assertTrue(textDiagnostics.isEmpty()) + assertTrue(r.v1.isEmpty()) + assertEquals('Hello, World!', r.v2) + } + } diff --git a/lib/src/test/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRendererTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRendererTests.groovy index 15c3b20..d949a37 100644 --- a/lib/src/test/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRendererTests.groovy +++ b/lib/src/test/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRendererTests.groovy @@ -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<>([], '

Hello, World!

\n')) + when(textRenderer.render(any(), any())) + .thenReturn(new Tuple2<>([], '

Hello, World!

\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('

Hello, World!

\n', r.v2) diff --git a/lib/src/test/groovy/com/jessebrault/ssg/template/GspTemplateRendererTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/template/GspTemplateRendererTests.groovy index 8fac879..d0c6470 100644 --- a/lib/src/test/groovy/com/jessebrault/ssg/template/GspTemplateRendererTests.groovy +++ b/lib/src/test/groovy/com/jessebrault/ssg/template/GspTemplateRendererTests.groovy @@ -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) } From 936587d0b58d41bda09d04ddeee762950b899a37 Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Sun, 12 Feb 2023 16:17:24 +0100 Subject: [PATCH 02/36] TagBuilder implemented and available in templates, parts, and specialPages. --- .../ssg/part/GspPartRenderer.groovy | 2 + .../specialpage/GspSpecialPageRenderer.groovy | 2 + .../ssg/tagbuilder/DynamicTagBuilder.groovy | 77 +++++++++++++++++++ .../ssg/tagbuilder/TagBuilder.groovy | 8 ++ .../ssg/template/GspTemplateRenderer.groovy | 2 + .../ssg/part/GspPartRendererTests.groovy | 8 ++ .../GspSpecialPageRendererTests.groovy | 8 ++ .../ssg/tagbuilder/TagBuilderTests.groovy | 54 +++++++++++++ .../template/GspTemplateRendererTests.groovy | 14 ++++ 9 files changed, 175 insertions(+) create mode 100644 lib/src/main/groovy/com/jessebrault/ssg/tagbuilder/DynamicTagBuilder.groovy create mode 100644 lib/src/main/groovy/com/jessebrault/ssg/tagbuilder/TagBuilder.groovy create mode 100644 lib/src/test/groovy/com/jessebrault/ssg/tagbuilder/TagBuilderTests.groovy diff --git a/lib/src/main/groovy/com/jessebrault/ssg/part/GspPartRenderer.groovy b/lib/src/main/groovy/com/jessebrault/ssg/part/GspPartRenderer.groovy index 84e7812..33ec3fb 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/part/GspPartRenderer.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/part/GspPartRenderer.groovy @@ -1,6 +1,7 @@ package com.jessebrault.ssg.part import com.jessebrault.ssg.Diagnostic +import com.jessebrault.ssg.tagbuilder.DynamicTagBuilder import com.jessebrault.ssg.text.EmbeddableText import groovy.text.GStringTemplateEngine import groovy.text.TemplateEngine @@ -26,6 +27,7 @@ class GspPartRenderer implements PartRenderer { def result = engine.createTemplate(part.text).make([ binding: binding, globals: globals, + tagBuilder: new DynamicTagBuilder(), text: text ]) new Tuple2<>([], result.toString()) diff --git a/lib/src/main/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRenderer.groovy b/lib/src/main/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRenderer.groovy index cadb675..6810e24 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRenderer.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRenderer.groovy @@ -3,6 +3,7 @@ package com.jessebrault.ssg.specialpage import com.jessebrault.ssg.Diagnostic import com.jessebrault.ssg.part.Part 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 groovy.text.GStringTemplateEngine @@ -25,6 +26,7 @@ class GspSpecialPageRenderer implements SpecialPageRenderer { parts: new EmbeddablePartsMap(parts, globals, { Collection partDiagnostics -> diagnostics.addAll(partDiagnostics) }), + tagBuilder: new DynamicTagBuilder(), texts: new EmbeddableTextsCollection(texts, globals, { Collection textDiagnostics -> diagnostics.addAll(textDiagnostics) }) diff --git a/lib/src/main/groovy/com/jessebrault/ssg/tagbuilder/DynamicTagBuilder.groovy b/lib/src/main/groovy/com/jessebrault/ssg/tagbuilder/DynamicTagBuilder.groovy new file mode 100644 index 0000000..9acb4d0 --- /dev/null +++ b/lib/src/main/groovy/com/jessebrault/ssg/tagbuilder/DynamicTagBuilder.groovy @@ -0,0 +1,77 @@ +package com.jessebrault.ssg.tagbuilder + +import org.codehaus.groovy.runtime.InvokerHelper + +class DynamicTagBuilder implements TagBuilder { + + @Override + String create(String name) { + "<$name />" + } + + @Override + String create(String name, Map attributes) { + def formattedAttributes = attributes.collect { + if (it.value instanceof String) { + it.key + '="' + it.value + '"' + } else if (it.value instanceof Integer) { + it.key + '=' + it.value + } else if (it.value instanceof Boolean && it.value == true) { + it.key + } else { + it.key + '="' + it.value.toString() + '"' + } + }.join(' ') + "<$name $formattedAttributes />" + } + + @Override + String create(String name, String body) { + "<$name>$body" + } + + @Override + String create(String name, Map attributes, String body) { + def formattedAttributes = attributes.collect { + if (it.value instanceof String) { + it.key + '="' + it.value + '"' + } else if (it.value instanceof Integer) { + it.key + '=' + it.value + } else if (it.value instanceof Boolean && it.value == true) { + it.key + } else { + it.key + '="' + it.value.toString() + '"' + } + }.join(' ') + "<$name $formattedAttributes>$body" + } + + @Override + Object invokeMethod(String name, Object args) { + def argsList = InvokerHelper.asList(args) + return switch (argsList.size()) { + case 0 -> this.create(name) + case 1 -> { + def arg0 = argsList[0] + if (arg0 instanceof Map) { + this.create(name, arg0) + } else if (arg0 instanceof String) { + this.create(name, arg0) + } else { + throw new MissingMethodException(name, this.class, args, false) + } + } + case 2 -> { + def arg0 = argsList[0] + def arg1 = argsList[1] + if (arg0 instanceof Map && arg1 instanceof String) { + this.create(name, arg0, arg1) + } else { + throw new MissingMethodException(name, this.class, args, false) + } + } + default -> throw new MissingMethodException(name, this.class, args, false) + } + } + +} diff --git a/lib/src/main/groovy/com/jessebrault/ssg/tagbuilder/TagBuilder.groovy b/lib/src/main/groovy/com/jessebrault/ssg/tagbuilder/TagBuilder.groovy new file mode 100644 index 0000000..a92c64a --- /dev/null +++ b/lib/src/main/groovy/com/jessebrault/ssg/tagbuilder/TagBuilder.groovy @@ -0,0 +1,8 @@ +package com.jessebrault.ssg.tagbuilder + +interface TagBuilder { + String create(String name) + String create(String name, Map attributes) + String create(String name, String body) + String create(String name, Map attributes, String body) +} diff --git a/lib/src/main/groovy/com/jessebrault/ssg/template/GspTemplateRenderer.groovy b/lib/src/main/groovy/com/jessebrault/ssg/template/GspTemplateRenderer.groovy index fc91f9d..9ad0842 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/template/GspTemplateRenderer.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/template/GspTemplateRenderer.groovy @@ -3,6 +3,7 @@ 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.tagbuilder.DynamicTagBuilder import com.jessebrault.ssg.text.EmbeddableText import com.jessebrault.ssg.text.FrontMatter import com.jessebrault.ssg.text.Text @@ -35,6 +36,7 @@ class GspTemplateRenderer implements TemplateRenderer { frontMatter: frontMatter, globals: globals, parts: new EmbeddablePartsMap(parts, globals, onDiagnostics, embeddableText), + tagBuilder: new DynamicTagBuilder(), text: embeddableText ]) new Tuple2<>(diagnostics, result.toString()) diff --git a/lib/src/test/groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy index 724b329..b759e59 100644 --- a/lib/src/test/groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy +++ b/lib/src/test/groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy @@ -68,4 +68,12 @@ class GspPartRendererTests { assertEquals('Hello, World!', r.v2) } + @Test + void tagBuilderAvailable() { + def part = new Part('', null, '<%= tagBuilder.test() %>') + def r = this.renderer.render(part, [:], [:], null) + assertTrue(r.v1.isEmpty()) + assertEquals('', r.v2) + } + } diff --git a/lib/src/test/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRendererTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRendererTests.groovy index d949a37..df0a597 100644 --- a/lib/src/test/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRendererTests.groovy +++ b/lib/src/test/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRendererTests.groovy @@ -75,4 +75,12 @@ class GspSpecialPageRendererTests { assertEquals('

Hello, World!

\n', r.v2) } + @Test + void tagBuilderAvailable() { + def specialPage = new SpecialPage('<%= tagBuilder.test() %>', null, null) + def r = this.renderer.render(specialPage, [], [], [:]) + assertTrue(r.v1.isEmpty()) + assertEquals('', r.v2) + } + } diff --git a/lib/src/test/groovy/com/jessebrault/ssg/tagbuilder/TagBuilderTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/tagbuilder/TagBuilderTests.groovy new file mode 100644 index 0000000..e598822 --- /dev/null +++ b/lib/src/test/groovy/com/jessebrault/ssg/tagbuilder/TagBuilderTests.groovy @@ -0,0 +1,54 @@ +package com.jessebrault.ssg.tagbuilder + +import org.junit.jupiter.api.Test + +import static org.junit.jupiter.api.Assertions.assertEquals + +class TagBuilderTests { + + private final TagBuilder tagBuilder = new DynamicTagBuilder() + + @Test + void simple() { + assertEquals('', this.tagBuilder.create('test')) + } + + @Test + void withAttributes() { + assertEquals('', this.tagBuilder.create('test', [test: 'abc'])) + } + + @Test + void withBody() { + assertEquals('test', this.tagBuilder.create('test', 'test')) + } + + @Test + void withAttributesAndBody() { + assertEquals( + 'test', + this.tagBuilder.create('test', [test: 'abc'], 'test') + ) + } + + @Test + void dynamicSimple() { + assertEquals('', this.tagBuilder.test()) + } + + @Test + void dynamicWithAttributes() { + assertEquals('', this.tagBuilder.test([test: 'abc'])) + } + + @Test + void dynamicWithBody() { + assertEquals('test', this.tagBuilder.test('test')) + } + + @Test + void dynamicWithAttributesAndBody() { + assertEquals('test', this.tagBuilder.test([test: 'abc'], 'test')) + } + +} diff --git a/lib/src/test/groovy/com/jessebrault/ssg/template/GspTemplateRendererTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/template/GspTemplateRendererTests.groovy index d0c6470..50684a7 100644 --- a/lib/src/test/groovy/com/jessebrault/ssg/template/GspTemplateRendererTests.groovy +++ b/lib/src/test/groovy/com/jessebrault/ssg/template/GspTemplateRendererTests.groovy @@ -136,4 +136,18 @@ class GspTemplateRendererTests { assertEquals('Hello, World!', r.v2) } + @Test + void tagBuilderAvailable() { + def template = new Template('<%= tagBuilder.test() %>', null, null) + def r = this.renderer.render( + template, + new FrontMatter(null, [:]), + mockBlankText(), + [], + [:] + ) + assertTrue(r.v1.isEmpty()) + assertEquals('', r.v2) + } + } From c608be5d9a2fda57fda8e628a55a05eeacf0c9ae Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Mon, 13 Feb 2023 13:45:27 +0100 Subject: [PATCH 03/36] Created CHANGELOG.md. --- CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9f17ced --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,33 @@ +# Changelog + +All notable changes to SSG will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to +[Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## Next/Unreleased + +### Added + +- A `tagBuilder` of type `DynamicTagBuilder` is available in Templates, SpecialPages, and Parts. + ```groovy + def simpleTag = tagBuilder.test() + assert simpleTag == '' + + def tagWithBody = tagBuilder.title 'Hello, World!' + assert tagWithBody == 'Hello, World!' + + def tagWithAttributes = tagBuilder.meta name: 'og:title', content: 'Hello, World!' + assert tagWithAttributes == '' + + def tagWithAttributesAndBody = tagBuilder.p([id: 'my-paragraph'], 'Hello, World!') + assert tagWithAttributesAndBody == '

Hello, World!

' + ``` + This is likely most useful for building simple, one-line html/xml tags. + [93687d](https://github.com/JesseBrault0709/ssg/commit/936587d). +- **Breaking**: the `text` object in Templates is now an instance of `EmbeddableText` instead of `String`. Thus, one must + use `text.render()` to obtain the rendered text. [34d9cd5](https://github.com/JesseBrault0709/ssg/commit/34d9cd5). +- Parts have a `text` object of type `EmbeddableText`. If we are rendering a part called from anything other than a Template + (which has an associated text), this will be `null`. [34d9cd5](https://github.com/JesseBrault0709/ssg/commit/34d9cd5). + +### Fixed From 0b57ec827c70de89499e0a8f66427f93f23095bf Mon Sep 17 00:00:00 2001 From: JesseBrault0709 <62299747+JesseBrault0709@users.noreply.github.com> Date: Mon, 13 Feb 2023 13:56:14 +0100 Subject: [PATCH 04/36] Update CHANGELOG.md --- CHANGELOG.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f17ced..2ffabf0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,14 +2,14 @@ All notable changes to SSG will be documented in this file. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to -[Semantic Versioning](https://semver.org/spec/v2.0.0.html). +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Next/Unreleased +## Next ### Added -- A `tagBuilder` of type `DynamicTagBuilder` is available in Templates, SpecialPages, and Parts. +- A `tagBuilder` object of type [`DynamicTagBuilder`](lib/src/main/groovy/com/jessebrault/ssg/tagbuilder/DynamicTagBuilder.groovy) is available in Templates, SpecialPages, and Parts. + ```groovy def simpleTag = tagBuilder.test() assert simpleTag == '' @@ -23,11 +23,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), def tagWithAttributesAndBody = tagBuilder.p([id: 'my-paragraph'], 'Hello, World!') assert tagWithAttributesAndBody == '

Hello, World!

' ``` - This is likely most useful for building simple, one-line html/xml tags. - [93687d](https://github.com/JesseBrault0709/ssg/commit/936587d). -- **Breaking**: the `text` object in Templates is now an instance of `EmbeddableText` instead of `String`. Thus, one must - use `text.render()` to obtain the rendered text. [34d9cd5](https://github.com/JesseBrault0709/ssg/commit/34d9cd5). -- Parts have a `text` object of type `EmbeddableText`. If we are rendering a part called from anything other than a Template - (which has an associated text), this will be `null`. [34d9cd5](https://github.com/JesseBrault0709/ssg/commit/34d9cd5). + + This is likely most useful for building simple, one-line html/xml tags. [93687d](https://github.com/JesseBrault0709/ssg/commit/936587d). +- Parts have a `text` object of type [`EmbeddableText`](lib/src/main/groovy/com/jessebrault/ssg/text/EmbeddableText.groovy). If one is rendering a Part called from anything other than a Template (which has an associated text), this will be `null`. [34d9cd5](https://github.com/JesseBrault0709/ssg/commit/34d9cd5). -### Fixed +### Changed +- **Breaking**: the `text` object in Templates is now an instance of [`EmbeddableText`](lib/src/main/groovy/com/jessebrault/ssg/text/EmbeddableText.groovy) instead of `String`. Thus, one must use `text.render()` to obtain the rendered text. [34d9cd5](https://github.com/JesseBrault0709/ssg/commit/34d9cd5). + +### Deprecated +- The `frontMatter` object in Templates is now deprecated. Use `text.frontMatter` instead. From eafc8cd336a5c4de81e8a966e23b46edc318b146 Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Mon, 13 Feb 2023 14:00:48 +0100 Subject: [PATCH 05/36] Deprecated frontMatter parameter in TemplateRenderer. --- .../com/jessebrault/ssg/template/GspTemplateRenderer.groovy | 1 + .../groovy/com/jessebrault/ssg/template/TemplateRenderer.groovy | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/src/main/groovy/com/jessebrault/ssg/template/GspTemplateRenderer.groovy b/lib/src/main/groovy/com/jessebrault/ssg/template/GspTemplateRenderer.groovy index 9ad0842..0e01342 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/template/GspTemplateRenderer.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/template/GspTemplateRenderer.groovy @@ -21,6 +21,7 @@ class GspTemplateRenderer implements TemplateRenderer { @Override Tuple2, String> render( Template template, + @Deprecated FrontMatter frontMatter, Text text, Collection parts, diff --git a/lib/src/main/groovy/com/jessebrault/ssg/template/TemplateRenderer.groovy b/lib/src/main/groovy/com/jessebrault/ssg/template/TemplateRenderer.groovy index 62d2021..15f23ad 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/template/TemplateRenderer.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/template/TemplateRenderer.groovy @@ -12,6 +12,7 @@ interface TemplateRenderer { */ Tuple2, String> render( Template template, + @Deprecated FrontMatter frontMatter, Text text, Collection parts, From e470b0ba909a812dac874b233795d0c2fd90ed26 Mon Sep 17 00:00:00 2001 From: JesseBrault0709 <62299747+JesseBrault0709@users.noreply.github.com> Date: Mon, 13 Feb 2023 14:06:55 +0100 Subject: [PATCH 06/36] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ffabf0..f009e91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,4 +31,4 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - **Breaking**: the `text` object in Templates is now an instance of [`EmbeddableText`](lib/src/main/groovy/com/jessebrault/ssg/text/EmbeddableText.groovy) instead of `String`. Thus, one must use `text.render()` to obtain the rendered text. [34d9cd5](https://github.com/JesseBrault0709/ssg/commit/34d9cd5). ### Deprecated -- The `frontMatter` object in Templates is now deprecated. Use `text.frontMatter` instead. +- The `frontMatter` object in Templates is now deprecated. Use `text.frontMatter` instead. [eafc8cd](https://github.com/JesseBrault0709/ssg/commit/eafc8cd). From 25f4b3657d8e92cff99c97e84f6c8aa656494d16 Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Mon, 13 Feb 2023 15:08:03 +0100 Subject: [PATCH 07/36] Refactored ssg.common to include everything, in preparation of text fixtures. --- buildSrc/src/main/groovy/ssg.common.gradle | 33 ++++++++++++++++++-- buildSrc/src/main/groovy/ssg.lib.gradle | 36 ---------------------- lib/build.gradle | 1 - 3 files changed, 31 insertions(+), 39 deletions(-) delete mode 100644 buildSrc/src/main/groovy/ssg.lib.gradle diff --git a/buildSrc/src/main/groovy/ssg.common.gradle b/buildSrc/src/main/groovy/ssg.common.gradle index 901fd91..ce03827 100644 --- a/buildSrc/src/main/groovy/ssg.common.gradle +++ b/buildSrc/src/main/groovy/ssg.common.gradle @@ -1,6 +1,8 @@ plugins { id 'com.jessebrault.jbarchiva' id 'groovy' + id 'java-library' + id 'java-test-fixtures' } group 'com.jessebrault.ssg' @@ -12,16 +14,43 @@ repositories { dependencies { // https://mvnrepository.com/artifact/org.apache.groovy/groovy - implementation 'org.apache.groovy:groovy:4.0.9' + api 'org.apache.groovy:groovy:4.0.9' + + // https://mvnrepository.com/artifact/org.jetbrains/annotations + api 'org.jetbrains:annotations:24.0.0' + + /** + * Logging + */ + // https://mvnrepository.com/artifact/org.slf4j/slf4j-api + implementation 'org.slf4j:slf4j-api:1.7.36' /** * TESTING */ // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api - testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2' + testFixturesApi 'org.junit.jupiter:junit-jupiter-api:5.9.2' // https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2' + + /** + * Mockito + */ + // https://mvnrepository.com/artifact/org.mockito/mockito-core + testFixturesApi 'org.mockito:mockito-core:4.11.0' + + // https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter + testFixturesApi 'org.mockito:mockito-junit-jupiter:4.11.0' + + /** + * Test Logging + */ + // https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl + testRuntimeOnly 'org.apache.logging.log4j:log4j-slf4j-impl:2.19.0' + + // https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core + testRuntimeOnly 'org.apache.logging.log4j:log4j-core:2.19.0' } test { diff --git a/buildSrc/src/main/groovy/ssg.lib.gradle b/buildSrc/src/main/groovy/ssg.lib.gradle deleted file mode 100644 index 78f61fa..0000000 --- a/buildSrc/src/main/groovy/ssg.lib.gradle +++ /dev/null @@ -1,36 +0,0 @@ -plugins { - id 'java-library' -} - -repositories { - mavenCentral() -} - -dependencies { - // https://mvnrepository.com/artifact/org.jetbrains/annotations - api 'org.jetbrains:annotations:24.0.0' - - /** - * Logging - */ - // https://mvnrepository.com/artifact/org.slf4j/slf4j-api - implementation 'org.slf4j:slf4j-api:1.7.36' - - /** - * Test Logging - */ - // https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl - testRuntimeOnly 'org.apache.logging.log4j:log4j-slf4j-impl:2.19.0' - - // https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core - testRuntimeOnly 'org.apache.logging.log4j:log4j-core:2.19.0' - - /** - * Mockito - */ - // https://mvnrepository.com/artifact/org.mockito/mockito-core - testImplementation 'org.mockito:mockito-core:4.11.0' - - // https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter - testImplementation 'org.mockito:mockito-junit-jupiter:4.11.0' -} \ No newline at end of file diff --git a/lib/build.gradle b/lib/build.gradle index 21b511d..53cb59f 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,6 +1,5 @@ plugins { id 'ssg.common' - id 'ssg.lib' } repositories { From 0e49414db707ca7ccc0e08e580575bd04c8eaf7e Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Mon, 13 Feb 2023 15:08:58 +0100 Subject: [PATCH 08/36] Parts now have access to all parts via a EmbeddablePartsMap. --- .../ssg/part/EmbeddablePart.groovy | 5 ++- .../ssg/part/EmbeddablePartsMap.groovy | 2 +- .../ssg/part/GspPartRenderer.groovy | 12 +++++-- .../jessebrault/ssg/part/PartRenderer.groovy | 3 +- .../ssg/part/GspPartRendererTests.groovy | 35 ++++++++++++++++--- .../ssg/testutil/DiagnosticsUtil.groovy | 18 ++++++++++ 6 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 lib/src/testFixtures/groovy/com/jessebrault/ssg/testutil/DiagnosticsUtil.groovy diff --git a/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePart.groovy b/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePart.groovy index ceeb31b..274716a 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePart.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePart.groovy @@ -18,12 +18,15 @@ class EmbeddablePart { @Nullable private final EmbeddableText text + private final Collection allParts + String render(Map binding = [:]) { def result = part.type.renderer.render( this.part, binding, this.globals, - this.text + this.text, + this.allParts ) if (result.v1.size() > 0) { this.onDiagnostics.call(result.v1) diff --git a/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePartsMap.groovy b/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePartsMap.groovy index 5473bdc..efff763 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePartsMap.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePartsMap.groovy @@ -20,7 +20,7 @@ class EmbeddablePartsMap { Objects.requireNonNull(globals) Objects.requireNonNull(onDiagnostics) parts.each { - this.put(it.path, new EmbeddablePart(it, globals, onDiagnostics, text)) + this.put(it.path, new EmbeddablePart(it, globals, onDiagnostics, text, parts)) } } diff --git a/lib/src/main/groovy/com/jessebrault/ssg/part/GspPartRenderer.groovy b/lib/src/main/groovy/com/jessebrault/ssg/part/GspPartRenderer.groovy index 33ec3fb..1e92479 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/part/GspPartRenderer.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/part/GspPartRenderer.groovy @@ -18,22 +18,30 @@ class GspPartRenderer implements PartRenderer { Part part, Map binding, Map globals, - @Nullable EmbeddableText text = null + @Nullable EmbeddableText text = null, + Collection allParts ) { Objects.requireNonNull(part) Objects.requireNonNull(binding) Objects.requireNonNull(globals) + Objects.requireNonNull(allParts) + def embeddedPartDiagnostics = [] try { def result = engine.createTemplate(part.text).make([ binding: binding, globals: globals, + parts: new EmbeddablePartsMap(allParts, globals, embeddedPartDiagnostics.&addAll, text), tagBuilder: new DynamicTagBuilder(), text: text ]) new Tuple2<>([], result.toString()) } catch (Exception e) { + def diagnostic = 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)], + [diagnostic, *embeddedPartDiagnostics], '' ) } diff --git a/lib/src/main/groovy/com/jessebrault/ssg/part/PartRenderer.groovy b/lib/src/main/groovy/com/jessebrault/ssg/part/PartRenderer.groovy index 2995b19..be508a5 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/part/PartRenderer.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/part/PartRenderer.groovy @@ -10,7 +10,8 @@ interface PartRenderer { Part part, Map binding, Map globals, - @Nullable EmbeddableText text + @Nullable EmbeddableText text, + Collection allParts ) } diff --git a/lib/src/test/groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy index b759e59..b488499 100644 --- a/lib/src/test/groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy +++ b/lib/src/test/groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy @@ -3,7 +3,9 @@ package com.jessebrault.ssg.part import com.jessebrault.ssg.Diagnostic import com.jessebrault.ssg.text.* import org.junit.jupiter.api.Test +import org.mockito.Mock +import static com.jessebrault.ssg.testutil.DiagnosticsUtil.getDiagnosticsMessageSupplier import static org.junit.jupiter.api.Assertions.assertEquals import static org.junit.jupiter.api.Assertions.assertTrue import static org.mockito.ArgumentMatchers.any @@ -28,7 +30,7 @@ class GspPartRendererTests { @Test void rendersWithNoBindingOrGlobals() { def part = new Part('', null, 'Hello, World!') - def r = this.renderer.render(part, [:], [:], null) + def r = this.renderer.render(part, [:], [:], null, [part]) assertTrue(r.v1.size() == 0) assertEquals('Hello, World!', r.v2) } @@ -36,7 +38,13 @@ class GspPartRendererTests { @Test void rendersWithBinding() { def part = new Part('', null, "<%= binding['greeting'] %>") - def r = this.renderer.render(part, [greeting: 'Hello, World!'], [:], null) + def r = this.renderer.render( + part, + [greeting: 'Hello, World!'], + [:], + null, + [part] + ) assertTrue(r.v1.size() == 0) assertEquals('Hello, World!', r.v2) } @@ -44,7 +52,13 @@ class GspPartRendererTests { @Test void rendersWithGlobals() { def part = new Part(null, null, "<%= globals['greeting'] %>") - def r = this.renderer.render(part, [:], [greeting: 'Hello, World!'], null) + def r = this.renderer.render( + part, + [:], + [greeting: 'Hello, World!'], + null, + [part] + ) assertTrue(r.v1.size() == 0) assertEquals('Hello, World!', r.v2) } @@ -61,7 +75,8 @@ class GspPartRendererTests { mockRenderableText('Hello, World!'), [:], { Collection diagnostics -> textDiagnostics.addAll(diagnostics) } - ) + ), + [part] ) assertTrue(textDiagnostics.isEmpty()) assertTrue(r.v1.isEmpty()) @@ -71,9 +86,19 @@ class GspPartRendererTests { @Test void tagBuilderAvailable() { def part = new Part('', null, '<%= tagBuilder.test() %>') - def r = this.renderer.render(part, [:], [:], null) + def r = this.renderer.render(part, [:], [:], null, [part]) assertTrue(r.v1.isEmpty()) assertEquals('', r.v2) } + @Test + void allPartsAvailable() { + 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]) + assertTrue(r.v1.isEmpty(), getDiagnosticsMessageSupplier(r.v1)) + assertEquals('Hello, World!', r.v2) + } + } diff --git a/lib/src/testFixtures/groovy/com/jessebrault/ssg/testutil/DiagnosticsUtil.groovy b/lib/src/testFixtures/groovy/com/jessebrault/ssg/testutil/DiagnosticsUtil.groovy new file mode 100644 index 0000000..10d4e16 --- /dev/null +++ b/lib/src/testFixtures/groovy/com/jessebrault/ssg/testutil/DiagnosticsUtil.groovy @@ -0,0 +1,18 @@ +package com.jessebrault.ssg.testutil + +import com.jessebrault.ssg.Diagnostic + +class DiagnosticsUtil { + + static Closure getDiagnosticsMessageSupplier(Collection diagnostics) { + return { + diagnostics.collect { + def writer = new StringWriter() + it.exception.printStackTrace(new PrintWriter(writer)) + def stackTrace = writer.toString() + "$it.message\n$stackTrace" + }.join('\n') + } + } + +} From 94c6edacba22dd3b29c7d5b1ca17c6b2ba2d093c Mon Sep 17 00:00:00 2001 From: JesseBrault0709 <62299747+JesseBrault0709@users.noreply.github.com> Date: Mon, 13 Feb 2023 15:15:09 +0100 Subject: [PATCH 09/36] Update CHANGELOG.md --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f009e91..34cd151 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ### Added +- Parts have access to all other parts now via `parts`, an object of type [`EmbeddablePartsMap`](lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePartsMap.groovy). For example: + + ```gsp + // myPart.gsp + <% out << parts['otherPart.gsp'].render() %> + ``` + + [0e49414](https://github.com/JesseBrault0709/ssg/commit/0e49414). - A `tagBuilder` object of type [`DynamicTagBuilder`](lib/src/main/groovy/com/jessebrault/ssg/tagbuilder/DynamicTagBuilder.groovy) is available in Templates, SpecialPages, and Parts. ```groovy From 5e2c682132958191946a6aac903f5113f46793fc Mon Sep 17 00:00:00 2001 From: JesseBrault0709 <62299747+JesseBrault0709@users.noreply.github.com> Date: Mon, 13 Feb 2023 15:15:41 +0100 Subject: [PATCH 10/36] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34cd151..159acdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,8 +35,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), This is likely most useful for building simple, one-line html/xml tags. [93687d](https://github.com/JesseBrault0709/ssg/commit/936587d). - Parts have a `text` object of type [`EmbeddableText`](lib/src/main/groovy/com/jessebrault/ssg/text/EmbeddableText.groovy). If one is rendering a Part called from anything other than a Template (which has an associated text), this will be `null`. [34d9cd5](https://github.com/JesseBrault0709/ssg/commit/34d9cd5). -### Changed -- **Breaking**: the `text` object in Templates is now an instance of [`EmbeddableText`](lib/src/main/groovy/com/jessebrault/ssg/text/EmbeddableText.groovy) instead of `String`. Thus, one must use `text.render()` to obtain the rendered text. [34d9cd5](https://github.com/JesseBrault0709/ssg/commit/34d9cd5). +### Breaking Changes +- The `text` object in Templates is now an instance of [`EmbeddableText`](lib/src/main/groovy/com/jessebrault/ssg/text/EmbeddableText.groovy) instead of `String`. Thus, one must use `text.render()` to obtain the rendered text. [34d9cd5](https://github.com/JesseBrault0709/ssg/commit/34d9cd5). ### Deprecated - The `frontMatter` object in Templates is now deprecated. Use `text.frontMatter` instead. [eafc8cd](https://github.com/JesseBrault0709/ssg/commit/eafc8cd). From 3b8cf46260a126868ac31138e8a40ffb369a0234 Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Mon, 13 Feb 2023 15:16:30 +0100 Subject: [PATCH 11/36] Cleaned up unused import. --- .../groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/test/groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy index b488499..48cfa42 100644 --- a/lib/src/test/groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy +++ b/lib/src/test/groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy @@ -3,7 +3,6 @@ package com.jessebrault.ssg.part import com.jessebrault.ssg.Diagnostic import com.jessebrault.ssg.text.* import org.junit.jupiter.api.Test -import org.mockito.Mock import static com.jessebrault.ssg.testutil.DiagnosticsUtil.getDiagnosticsMessageSupplier import static org.junit.jupiter.api.Assertions.assertEquals From 0371d41ccc4fb37df4681b967cf160e929d4e7bc Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Tue, 14 Feb 2023 13:19:28 +0100 Subject: [PATCH 12/36] Added path and urlBuilder to gsp dsl. --- .../ssg/part/EmbeddablePart.groovy | 5 +- .../ssg/part/EmbeddablePartsMap.groovy | 5 +- .../ssg/part/GspPartRenderer.groovy | 11 ++- .../jessebrault/ssg/part/PartRenderer.groovy | 3 +- .../specialpage/GspSpecialPageRenderer.groovy | 16 ++-- .../ssg/template/GspTemplateRenderer.groovy | 7 +- .../ssg/url/PathBasedUrlBuilder.groovy | 23 +++++ .../com/jessebrault/ssg/url/UrlBuilder.groovy | 5 ++ .../ssg/part/GspPartRendererTests.groovy | 70 +++++++++------ .../GspSpecialPageRendererTests.groovy | 50 ++++++++--- .../template/GspTemplateRendererTests.groovy | 86 ++++++++++--------- .../ssg/url/PathBasedUrlBuilderTests.groovy | 10 +++ .../ssg/testutil/DiagnosticsUtil.groovy | 6 ++ .../com/jessebrault/ssg/text/TextMocks.groovy | 37 ++++++++ .../ssg/url/AbstractUrlBuilderTests.groovy | 33 +++++++ 15 files changed, 271 insertions(+), 96 deletions(-) create mode 100644 lib/src/main/groovy/com/jessebrault/ssg/url/PathBasedUrlBuilder.groovy create mode 100644 lib/src/main/groovy/com/jessebrault/ssg/url/UrlBuilder.groovy create mode 100644 lib/src/test/groovy/com/jessebrault/ssg/url/PathBasedUrlBuilderTests.groovy create mode 100644 lib/src/testFixtures/groovy/com/jessebrault/ssg/text/TextMocks.groovy create mode 100644 lib/src/testFixtures/groovy/com/jessebrault/ssg/url/AbstractUrlBuilderTests.groovy diff --git a/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePart.groovy b/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePart.groovy index 274716a..e696335 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePart.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePart.groovy @@ -20,13 +20,16 @@ class EmbeddablePart { private final Collection 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) diff --git a/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePartsMap.groovy b/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePartsMap.groovy index efff763..290f8ee 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePartsMap.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/part/EmbeddablePartsMap.groovy @@ -14,13 +14,14 @@ class EmbeddablePartsMap { Collection 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)) } } diff --git a/lib/src/main/groovy/com/jessebrault/ssg/part/GspPartRenderer.groovy b/lib/src/main/groovy/com/jessebrault/ssg/part/GspPartRenderer.groovy index 1e92479..f4716f7 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/part/GspPartRenderer.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/part/GspPartRenderer.groovy @@ -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 allParts + Collection 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) { diff --git a/lib/src/main/groovy/com/jessebrault/ssg/part/PartRenderer.groovy b/lib/src/main/groovy/com/jessebrault/ssg/part/PartRenderer.groovy index be508a5..7c7dc3c 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/part/PartRenderer.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/part/PartRenderer.groovy @@ -11,7 +11,8 @@ interface PartRenderer { Map binding, Map globals, @Nullable EmbeddableText text, - Collection allParts + Collection allParts, + String path ) } diff --git a/lib/src/main/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRenderer.groovy b/lib/src/main/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRenderer.groovy index 6810e24..df607f1 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRenderer.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRenderer.groovy @@ -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, String> render(SpecialPage specialPage, Collection texts, Collection parts, Map globals) { + Tuple2, String> render( + SpecialPage specialPage, + Collection texts, + Collection parts, + Map globals + ) { try { Collection diagnostics = [] def result = engine.createTemplate(specialPage.text).make([ globals: globals, - parts: new EmbeddablePartsMap(parts, globals, { Collection 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 textDiagnostics -> diagnostics.addAll(textDiagnostics) - }) + }), + urlBuilder: new PathBasedUrlBuilder(specialPage.path) ]) new Tuple2<>(diagnostics, result.toString()) } catch (Exception e) { diff --git a/lib/src/main/groovy/com/jessebrault/ssg/template/GspTemplateRenderer.groovy b/lib/src/main/groovy/com/jessebrault/ssg/template/GspTemplateRenderer.groovy index 0e01342..901b90e 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/template/GspTemplateRenderer.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/template/GspTemplateRenderer.groovy @@ -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) { diff --git a/lib/src/main/groovy/com/jessebrault/ssg/url/PathBasedUrlBuilder.groovy b/lib/src/main/groovy/com/jessebrault/ssg/url/PathBasedUrlBuilder.groovy new file mode 100644 index 0000000..c4065f3 --- /dev/null +++ b/lib/src/main/groovy/com/jessebrault/ssg/url/PathBasedUrlBuilder.groovy @@ -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() + } + +} diff --git a/lib/src/main/groovy/com/jessebrault/ssg/url/UrlBuilder.groovy b/lib/src/main/groovy/com/jessebrault/ssg/url/UrlBuilder.groovy new file mode 100644 index 0000000..d7a2a26 --- /dev/null +++ b/lib/src/main/groovy/com/jessebrault/ssg/url/UrlBuilder.groovy @@ -0,0 +1,5 @@ +package com.jessebrault.ssg.url + +interface UrlBuilder { + String relative(String to) +} \ No newline at end of file diff --git a/lib/src/test/groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy index 48cfa42..49165ab 100644 --- a/lib/src/test/groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy +++ b/lib/src/test/groovy/com/jessebrault/ssg/part/GspPartRendererTests.groovy @@ -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 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('', 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) + } + } diff --git a/lib/src/test/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRendererTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRendererTests.groovy index df0a597..5a61fcb 100644 --- a/lib/src/test/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRendererTests.groovy +++ b/lib/src/test/groovy/com/jessebrault/ssg/specialpage/GspSpecialPageRendererTests.groovy @@ -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('

Hello, World!

\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('', 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) + } + } diff --git a/lib/src/test/groovy/com/jessebrault/ssg/template/GspTemplateRendererTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/template/GspTemplateRendererTests.groovy index 50684a7..5b301ea 100644 --- a/lib/src/test/groovy/com/jessebrault/ssg/template/GspTemplateRendererTests.groovy +++ b/lib/src/test/groovy/com/jessebrault/ssg/template/GspTemplateRendererTests.groovy @@ -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('', 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) + } + } diff --git a/lib/src/test/groovy/com/jessebrault/ssg/url/PathBasedUrlBuilderTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/url/PathBasedUrlBuilderTests.groovy new file mode 100644 index 0000000..4381779 --- /dev/null +++ b/lib/src/test/groovy/com/jessebrault/ssg/url/PathBasedUrlBuilderTests.groovy @@ -0,0 +1,10 @@ +package com.jessebrault.ssg.url + +class PathBasedUrlBuilderTests extends AbstractUrlBuilderTests { + + @Override + protected UrlBuilder getUrlBuilder(String fromFile) { + new PathBasedUrlBuilder(fromFile) + } + +} diff --git a/lib/src/testFixtures/groovy/com/jessebrault/ssg/testutil/DiagnosticsUtil.groovy b/lib/src/testFixtures/groovy/com/jessebrault/ssg/testutil/DiagnosticsUtil.groovy index 10d4e16..6405933 100644 --- a/lib/src/testFixtures/groovy/com/jessebrault/ssg/testutil/DiagnosticsUtil.groovy +++ b/lib/src/testFixtures/groovy/com/jessebrault/ssg/testutil/DiagnosticsUtil.groovy @@ -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 getDiagnosticsMessageSupplier(Collection diagnostics) { @@ -15,4 +17,8 @@ class DiagnosticsUtil { } } + static void assertEmptyDiagnostics(Tuple2, ?> result) { + assertTrue(result.v1.isEmpty(), getDiagnosticsMessageSupplier(result.v1)) + } + } diff --git a/lib/src/testFixtures/groovy/com/jessebrault/ssg/text/TextMocks.groovy b/lib/src/testFixtures/groovy/com/jessebrault/ssg/text/TextMocks.groovy new file mode 100644 index 0000000..b27b2d1 --- /dev/null +++ b/lib/src/testFixtures/groovy/com/jessebrault/ssg/text/TextMocks.groovy @@ -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)) + } + +} diff --git a/lib/src/testFixtures/groovy/com/jessebrault/ssg/url/AbstractUrlBuilderTests.groovy b/lib/src/testFixtures/groovy/com/jessebrault/ssg/url/AbstractUrlBuilderTests.groovy new file mode 100644 index 0000000..ebbae9b --- /dev/null +++ b/lib/src/testFixtures/groovy/com/jessebrault/ssg/url/AbstractUrlBuilderTests.groovy @@ -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') + ) + } + +} From 99836856d77ff2d643ed373704b7e0056e138864 Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Tue, 14 Feb 2023 14:41:24 +0100 Subject: [PATCH 13/36] Refactored extensions related into static ExtensionsUtil. --- .../ssg/part/PartFilePartsProvider.groovy | 6 ++- ...SpecialPageFileSpecialPagesProvider.groovy | 10 ++-- .../TemplateFileTemplatesProvider.groovy | 6 ++- .../ssg/text/TextFileTextsProvider.groovy | 10 ++-- .../ssg/util/ExtensionsUtil.groovy | 24 +++++++++ .../ssg/util/FileNameHandler.groovy | 32 ------------ .../ssg/util/RelativePathHandler.groovy | 23 --------- .../ssg/util/ExtensionsUtilTests.groovy | 49 +++++++++++++++++++ .../ssg/util/FileNameHandlerTests.groovy | 16 ------ .../ssg/util/RelativePathHandlerTests.groovy | 15 ------ 10 files changed, 93 insertions(+), 98 deletions(-) create mode 100644 lib/src/main/groovy/com/jessebrault/ssg/util/ExtensionsUtil.groovy delete mode 100644 lib/src/main/groovy/com/jessebrault/ssg/util/FileNameHandler.groovy delete mode 100644 lib/src/main/groovy/com/jessebrault/ssg/util/RelativePathHandler.groovy create mode 100644 lib/src/test/groovy/com/jessebrault/ssg/util/ExtensionsUtilTests.groovy delete mode 100644 lib/src/test/groovy/com/jessebrault/ssg/util/FileNameHandlerTests.groovy delete mode 100644 lib/src/test/groovy/com/jessebrault/ssg/util/RelativePathHandlerTests.groovy diff --git a/lib/src/main/groovy/com/jessebrault/ssg/part/PartFilePartsProvider.groovy b/lib/src/main/groovy/com/jessebrault/ssg/part/PartFilePartsProvider.groovy index 72e7cc8..b49f117 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/part/PartFilePartsProvider.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/part/PartFilePartsProvider.groovy @@ -1,13 +1,14 @@ package com.jessebrault.ssg.part import com.jessebrault.ssg.provider.WithWatchableDir -import com.jessebrault.ssg.util.FileNameHandler import groovy.io.FileType import groovy.transform.EqualsAndHashCode import groovy.transform.NullCheck import org.slf4j.Logger import org.slf4j.LoggerFactory +import static com.jessebrault.ssg.util.ExtensionsUtil.getExtension + @NullCheck @EqualsAndHashCode(includeFields = true) class PartFilePartsProvider implements PartsProvider, WithWatchableDir { @@ -24,8 +25,9 @@ class PartFilePartsProvider implements PartsProvider, WithWatchableDir { } private PartType getPartType(File file) { + def path = file.path this.partTypes.find { - it.ids.contains(new FileNameHandler(file).getExtension()) + it.ids.contains(getExtension(path)) } } diff --git a/lib/src/main/groovy/com/jessebrault/ssg/specialpage/SpecialPageFileSpecialPagesProvider.groovy b/lib/src/main/groovy/com/jessebrault/ssg/specialpage/SpecialPageFileSpecialPagesProvider.groovy index a2ef1e3..37d6a9d 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/specialpage/SpecialPageFileSpecialPagesProvider.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/specialpage/SpecialPageFileSpecialPagesProvider.groovy @@ -1,14 +1,15 @@ package com.jessebrault.ssg.specialpage import com.jessebrault.ssg.provider.WithWatchableDir -import com.jessebrault.ssg.util.FileNameHandler -import com.jessebrault.ssg.util.RelativePathHandler import groovy.io.FileType import groovy.transform.EqualsAndHashCode import groovy.transform.NullCheck import org.slf4j.Logger import org.slf4j.LoggerFactory +import static com.jessebrault.ssg.util.ExtensionsUtil.getExtension +import static com.jessebrault.ssg.util.ExtensionsUtil.stripExtension + @NullCheck @EqualsAndHashCode(includeFields = true) class SpecialPageFileSpecialPagesProvider implements SpecialPagesProvider, WithWatchableDir { @@ -25,8 +26,9 @@ class SpecialPageFileSpecialPagesProvider implements SpecialPagesProvider, WithW } private SpecialPageType getSpecialPageType(File file) { + def path = file.path this.specialPageTypes.find { - it.ids.contains(new FileNameHandler(file).getExtension()) + it.ids.contains(getExtension(path)) } } @@ -41,7 +43,7 @@ class SpecialPageFileSpecialPagesProvider implements SpecialPagesProvider, WithW def type = this.getSpecialPageType(it) if (type != null) { def relativePath = this.specialPagesDir.relativePath(it) - def path = new RelativePathHandler(relativePath).getWithoutExtension() + def path = stripExtension(relativePath) logger.info('found specialPage {} with type {}', path, type) specialPages << new SpecialPage(it.text, path, type) } else { diff --git a/lib/src/main/groovy/com/jessebrault/ssg/template/TemplateFileTemplatesProvider.groovy b/lib/src/main/groovy/com/jessebrault/ssg/template/TemplateFileTemplatesProvider.groovy index a656c1e..09bcc18 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/template/TemplateFileTemplatesProvider.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/template/TemplateFileTemplatesProvider.groovy @@ -1,13 +1,14 @@ package com.jessebrault.ssg.template import com.jessebrault.ssg.provider.WithWatchableDir -import com.jessebrault.ssg.util.FileNameHandler import groovy.io.FileType import groovy.transform.EqualsAndHashCode import groovy.transform.NullCheck import org.slf4j.Logger import org.slf4j.LoggerFactory +import static com.jessebrault.ssg.util.ExtensionsUtil.getExtension + @NullCheck @EqualsAndHashCode(includeFields = true) class TemplateFileTemplatesProvider implements TemplatesProvider, WithWatchableDir { @@ -24,8 +25,9 @@ class TemplateFileTemplatesProvider implements TemplatesProvider, WithWatchableD } private TemplateType getType(File file) { + def path = file.path this.templateTypes.find { - it.ids.contains(new FileNameHandler(file).getExtension()) + it.ids.contains(getExtension(path)) } } diff --git a/lib/src/main/groovy/com/jessebrault/ssg/text/TextFileTextsProvider.groovy b/lib/src/main/groovy/com/jessebrault/ssg/text/TextFileTextsProvider.groovy index 1eacbf9..3e6510f 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/text/TextFileTextsProvider.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/text/TextFileTextsProvider.groovy @@ -1,14 +1,15 @@ package com.jessebrault.ssg.text import com.jessebrault.ssg.provider.WithWatchableDir -import com.jessebrault.ssg.util.FileNameHandler -import com.jessebrault.ssg.util.RelativePathHandler import groovy.io.FileType import groovy.transform.EqualsAndHashCode import groovy.transform.NullCheck import org.slf4j.Logger import org.slf4j.LoggerFactory +import static com.jessebrault.ssg.util.ExtensionsUtil.getExtension +import static com.jessebrault.ssg.util.ExtensionsUtil.stripExtension + @NullCheck @EqualsAndHashCode(includeFields = true) class TextFileTextsProvider implements TextsProvider, WithWatchableDir { @@ -25,8 +26,9 @@ class TextFileTextsProvider implements TextsProvider, WithWatchableDir { } private TextType getTextType(File file) { + def path = file.path this.textTypes.find { - it.ids.contains(new FileNameHandler(file).getExtension()) + it.ids.contains(getExtension(path)) } } @@ -41,7 +43,7 @@ class TextFileTextsProvider implements TextsProvider, WithWatchableDir { def type = this.getTextType(it) if (type != null) { def relativePath = this.textsDir.relativePath(it) - def path = new RelativePathHandler(relativePath).getWithoutExtension() + def path = stripExtension(relativePath) logger.debug('found textFile {} with type {}', path, type) textFiles << new Text(it.text, path, type) } else { diff --git a/lib/src/main/groovy/com/jessebrault/ssg/util/ExtensionsUtil.groovy b/lib/src/main/groovy/com/jessebrault/ssg/util/ExtensionsUtil.groovy new file mode 100644 index 0000000..b072c4c --- /dev/null +++ b/lib/src/main/groovy/com/jessebrault/ssg/util/ExtensionsUtil.groovy @@ -0,0 +1,24 @@ +package com.jessebrault.ssg.util + +import java.util.regex.Pattern + +class ExtensionsUtil { + + private static final Pattern stripExtensionPattern = ~/(.+)\..+$/ + private static final Pattern getExtensionPattern = ~/.+(\..+)$/ + + static String stripExtension(String path) { + def m = stripExtensionPattern.matcher(path) + m.matches() ? m.group(1) : path + } + + static String getExtension(String path) { + def m = getExtensionPattern.matcher(path) + if (m.matches()) { + m.group(1) + } else { + throw new IllegalArgumentException("cannot get extension for path: ${ path }") + } + } + +} diff --git a/lib/src/main/groovy/com/jessebrault/ssg/util/FileNameHandler.groovy b/lib/src/main/groovy/com/jessebrault/ssg/util/FileNameHandler.groovy deleted file mode 100644 index 086cf35..0000000 --- a/lib/src/main/groovy/com/jessebrault/ssg/util/FileNameHandler.groovy +++ /dev/null @@ -1,32 +0,0 @@ -package com.jessebrault.ssg.util - -import groovy.transform.EqualsAndHashCode -import groovy.transform.NullCheck -import groovy.transform.TupleConstructor - -@TupleConstructor(includeFields = true, defaults = false) -@NullCheck -@EqualsAndHashCode(includeFields = true) -class FileNameHandler { - - private final File file - - String getExtension() { - def lastIndexOfDot = this.file.name.lastIndexOf('.') - if (lastIndexOfDot == -1) { - '' - } else { - this.file.name.substring(lastIndexOfDot) - } - } - - String getWithoutExtension() { - this.file.name.substring(0, this.file.name.lastIndexOf('.')) - } - - @Override - String toString() { - "FileNameHandler(file: ${ this.file })" - } - -} diff --git a/lib/src/main/groovy/com/jessebrault/ssg/util/RelativePathHandler.groovy b/lib/src/main/groovy/com/jessebrault/ssg/util/RelativePathHandler.groovy deleted file mode 100644 index 2258bb0..0000000 --- a/lib/src/main/groovy/com/jessebrault/ssg/util/RelativePathHandler.groovy +++ /dev/null @@ -1,23 +0,0 @@ -package com.jessebrault.ssg.util - -import groovy.transform.EqualsAndHashCode -import groovy.transform.NullCheck -import groovy.transform.TupleConstructor - -@TupleConstructor(includeFields = true, defaults = false) -@NullCheck -@EqualsAndHashCode(includeFields = true) -class RelativePathHandler { - - private final String relativePath - - String getWithoutExtension() { - this.relativePath.subSequence(0, this.relativePath.lastIndexOf('.')) - } - - @Override - String toString() { - "RelativePathHandler(relativePath: ${ this.relativePath })" - } - -} diff --git a/lib/src/test/groovy/com/jessebrault/ssg/util/ExtensionsUtilTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/util/ExtensionsUtilTests.groovy new file mode 100644 index 0000000..438c3c8 --- /dev/null +++ b/lib/src/test/groovy/com/jessebrault/ssg/util/ExtensionsUtilTests.groovy @@ -0,0 +1,49 @@ +package com.jessebrault.ssg.util + +import org.junit.jupiter.api.Test + +import static com.jessebrault.ssg.util.ExtensionsUtil.stripExtension +import static com.jessebrault.ssg.util.ExtensionsUtil.getExtension +import static org.junit.jupiter.api.Assertions.assertEquals + +class ExtensionsUtilTests { + + static class StripExtensionTests { + + @Test + void simple() { + assertEquals('test', stripExtension('test.txt')) + } + + @Test + void withSlashes() { + assertEquals('test/test', stripExtension('test/test.txt')) + } + + @Test + void withMultipleExtensions() { + assertEquals('test.txt', stripExtension('test.txt.html')) + } + + } + + static class GetExtensionTests { + + @Test + void simple() { + assertEquals('.txt', getExtension('test.txt')) + } + + @Test + void withSlashes() { + assertEquals('.txt', getExtension('test/test.txt')) + } + + @Test + void withMultipleExtensions() { + assertEquals('.txt', getExtension('test.test.txt')) + } + + } + +} diff --git a/lib/src/test/groovy/com/jessebrault/ssg/util/FileNameHandlerTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/util/FileNameHandlerTests.groovy deleted file mode 100644 index d15f57b..0000000 --- a/lib/src/test/groovy/com/jessebrault/ssg/util/FileNameHandlerTests.groovy +++ /dev/null @@ -1,16 +0,0 @@ -package com.jessebrault.ssg.util - -import org.junit.jupiter.api.Test - -import static org.junit.jupiter.api.Assertions.assertEquals - -class FileNameHandlerTests { - - @Test - void getsCorrectExtension() { - def file = new File('hello.txt') - def extension = new FileNameHandler(file).getExtension() - assertEquals('.txt', extension) - } - -} diff --git a/lib/src/test/groovy/com/jessebrault/ssg/util/RelativePathHandlerTests.groovy b/lib/src/test/groovy/com/jessebrault/ssg/util/RelativePathHandlerTests.groovy deleted file mode 100644 index a82f87e..0000000 --- a/lib/src/test/groovy/com/jessebrault/ssg/util/RelativePathHandlerTests.groovy +++ /dev/null @@ -1,15 +0,0 @@ -package com.jessebrault.ssg.util - -import org.junit.jupiter.api.Test - -import static org.junit.jupiter.api.Assertions.assertEquals - -class RelativePathHandlerTests { - - @Test - void stripsExtension() { - def stripped = new RelativePathHandler('hello.txt').getWithoutExtension() - assertEquals('hello', stripped) - } - -} From 076bc9bc52320e83994ed3c254314efdd799847b Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Tue, 14 Feb 2023 15:33:15 +0100 Subject: [PATCH 14/36] Refactoring of all file providers; all paths now include extension. --- .../ssg/AbstractBuildCommand.groovy | 8 +-- .../ssg/part/PartFilePartsProvider.groovy | 45 ++++++----------- .../AbstractFileCollectionProvider.groovy | 40 +++++++++++++++ ...SpecialPageFileSpecialPagesProvider.groovy | 49 ++++++------------- .../TemplateFileTemplatesProvider.groovy | 45 ++++++----------- .../ssg/text/TextFileTextsProvider.groovy | 47 ++++++------------ ...StaticSiteGeneratorIntegrationTests.groovy | 8 +-- .../part/PartFilePartsProviderTests.groovy | 5 +- ...alPageFileSpecialPagesProviderTests.groovy | 9 ++-- ...TemplateFileTemplatesProviderTests.groovy} | 7 +-- .../text/TextFileTextsProviderTests.groovy | 14 ++++-- 11 files changed, 128 insertions(+), 149 deletions(-) create mode 100644 lib/src/main/groovy/com/jessebrault/ssg/provider/AbstractFileCollectionProvider.groovy rename lib/src/test/groovy/com/jessebrault/ssg/template/{PageTemplatesProviderTests.groovy => TemplateFileTemplatesProviderTests.groovy} (90%) diff --git a/cli/src/main/groovy/com/jessebrault/ssg/AbstractBuildCommand.groovy b/cli/src/main/groovy/com/jessebrault/ssg/AbstractBuildCommand.groovy index cc087f7..80b8cd0 100644 --- a/cli/src/main/groovy/com/jessebrault/ssg/AbstractBuildCommand.groovy +++ b/cli/src/main/groovy/com/jessebrault/ssg/AbstractBuildCommand.groovy @@ -32,10 +32,10 @@ abstract class AbstractBuildCommand extends AbstractSubCommand { def gspPart = new PartType(['.gsp'], new GspPartRenderer()) def gspSpecialPage = new SpecialPageType(['.gsp'], new GspSpecialPageRenderer()) - def defaultTextsProvider = new TextFileTextsProvider([markdownText], new File('texts')) - def defaultTemplatesProvider = new TemplateFileTemplatesProvider([gspTemplate], new File('templates')) - def defaultPartsProvider = new PartFilePartsProvider([gspPart], new File('parts')) - def defaultSpecialPagesProvider = new SpecialPageFileSpecialPagesProvider([gspSpecialPage], new File('specialPages')) + def defaultTextsProvider = new TextFileTextsProvider(new File('texts'), [markdownText]) + def defaultTemplatesProvider = new TemplateFileTemplatesProvider(new File('templates'), [gspTemplate]) + def defaultPartsProvider = new PartFilePartsProvider(new File('parts'), [gspPart]) + def defaultSpecialPagesProvider = new SpecialPageFileSpecialPagesProvider(new File('specialPages'), [gspSpecialPage]) def defaultConfig = new Config( textProviders: [defaultTextsProvider], diff --git a/lib/src/main/groovy/com/jessebrault/ssg/part/PartFilePartsProvider.groovy b/lib/src/main/groovy/com/jessebrault/ssg/part/PartFilePartsProvider.groovy index b49f117..f445690 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/part/PartFilePartsProvider.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/part/PartFilePartsProvider.groovy @@ -1,55 +1,38 @@ package com.jessebrault.ssg.part -import com.jessebrault.ssg.provider.WithWatchableDir -import groovy.io.FileType +import com.jessebrault.ssg.provider.AbstractFileCollectionProvider import groovy.transform.EqualsAndHashCode import groovy.transform.NullCheck +import org.jetbrains.annotations.Nullable import org.slf4j.Logger import org.slf4j.LoggerFactory -import static com.jessebrault.ssg.util.ExtensionsUtil.getExtension - @NullCheck @EqualsAndHashCode(includeFields = true) -class PartFilePartsProvider implements PartsProvider, WithWatchableDir { +class PartFilePartsProvider extends AbstractFileCollectionProvider implements PartsProvider { private static final Logger logger = LoggerFactory.getLogger(PartFilePartsProvider) private final Collection partTypes - private final File partsDir - PartFilePartsProvider(Collection partTypes, File partsDir) { - this.partTypes = partTypes - this.partsDir = partsDir - this.watchableDir = this.partsDir + PartFilePartsProvider(File partsDir, Collection partTypes) { + super(partsDir) + this.partTypes = Objects.requireNonNull(partTypes) } - private PartType getPartType(File file) { - def path = file.path + private @Nullable PartType getPartType(String extension) { this.partTypes.find { - it.ids.contains(getExtension(path)) + it.ids.contains(extension) } } @Override - Collection provide() { - if (!partsDir.isDirectory()) { - logger.warn('partsDir {} does not exist or is not a directory; skipping and providing no Parts', this.partsDir) - [] - } else { - def parts = [] - this.partsDir.eachFileRecurse(FileType.FILES) { - def type = this.getPartType(it) - if (type != null) { - def relativePath = this.partsDir.relativePath(it) - logger.debug('found part {}', relativePath) - parts << new Part(relativePath, type, it.text) - } else { - logger.warn('ignoring {} since there is no partType for it', it) - } - } - parts + protected @Nullable Part transformFileToT(File file, String relativePath, String extension) { + def partType = getPartType(extension) + if (!partType) { + logger.warn('there is no PartType for {}, ignoring', relativePath) } + partType ? new Part(relativePath, partType, file.text) : null } @Override @@ -59,7 +42,7 @@ class PartFilePartsProvider implements PartsProvider, WithWatchableDir { @Override String toString() { - "PartFilePartsProvider(partsDir: ${ this.partsDir }, partTypes: ${ this.partTypes })" + "PartFilePartsProvider(partsDir: ${ this.dir }, partTypes: ${ this.partTypes })" } } diff --git a/lib/src/main/groovy/com/jessebrault/ssg/provider/AbstractFileCollectionProvider.groovy b/lib/src/main/groovy/com/jessebrault/ssg/provider/AbstractFileCollectionProvider.groovy new file mode 100644 index 0000000..56ae2c7 --- /dev/null +++ b/lib/src/main/groovy/com/jessebrault/ssg/provider/AbstractFileCollectionProvider.groovy @@ -0,0 +1,40 @@ +package com.jessebrault.ssg.provider + +import groovy.io.FileType +import org.jetbrains.annotations.Nullable +import org.slf4j.Logger +import org.slf4j.LoggerFactory + +import static com.jessebrault.ssg.util.ExtensionsUtil.getExtension + +abstract class AbstractFileCollectionProvider implements Provider>, WithWatchableDir { + + private static final Logger logger = LoggerFactory.getLogger(AbstractFileCollectionProvider) + + protected final File dir + + AbstractFileCollectionProvider(File dir) { + this.dir = Objects.requireNonNull(dir) + this.watchableDir = dir + } + + protected abstract @Nullable T transformFileToT(File file, String relativePath, String extension) + + @Override + Collection provide() { + if (!this.dir.isDirectory()) { + logger.warn('{} does not exist or is not a directory; skipping', this.dir) + [] + } else { + def ts = [] + this.dir.eachFileRecurse(FileType.FILES) { + def t = transformFileToT(it, this.dir.relativePath(it), getExtension(it.path)) + if (t) { + ts << t + } + } + ts + } + } + +} diff --git a/lib/src/main/groovy/com/jessebrault/ssg/specialpage/SpecialPageFileSpecialPagesProvider.groovy b/lib/src/main/groovy/com/jessebrault/ssg/specialpage/SpecialPageFileSpecialPagesProvider.groovy index 37d6a9d..a8d9f9c 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/specialpage/SpecialPageFileSpecialPagesProvider.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/specialpage/SpecialPageFileSpecialPagesProvider.groovy @@ -1,57 +1,39 @@ package com.jessebrault.ssg.specialpage -import com.jessebrault.ssg.provider.WithWatchableDir -import groovy.io.FileType +import com.jessebrault.ssg.provider.AbstractFileCollectionProvider import groovy.transform.EqualsAndHashCode import groovy.transform.NullCheck +import org.jetbrains.annotations.Nullable import org.slf4j.Logger import org.slf4j.LoggerFactory -import static com.jessebrault.ssg.util.ExtensionsUtil.getExtension -import static com.jessebrault.ssg.util.ExtensionsUtil.stripExtension - @NullCheck @EqualsAndHashCode(includeFields = true) -class SpecialPageFileSpecialPagesProvider implements SpecialPagesProvider, WithWatchableDir { +class SpecialPageFileSpecialPagesProvider extends AbstractFileCollectionProvider + implements SpecialPagesProvider { private static final Logger logger = LoggerFactory.getLogger(SpecialPageFileSpecialPagesProvider) private final Collection specialPageTypes - private final File specialPagesDir - SpecialPageFileSpecialPagesProvider(Collection specialPageTypes, File specialPagesDir) { - this.specialPageTypes = specialPageTypes - this.specialPagesDir = specialPagesDir - this.watchableDir = this.specialPagesDir + SpecialPageFileSpecialPagesProvider(File specialPagesDir, Collection specialPageTypes) { + super(specialPagesDir) + this.specialPageTypes = Objects.requireNonNull(specialPageTypes) } - private SpecialPageType getSpecialPageType(File file) { - def path = file.path + private @Nullable SpecialPageType getSpecialPageType(String extension) { this.specialPageTypes.find { - it.ids.contains(getExtension(path)) + it.ids.contains(extension) } } @Override - Collection provide() { - if (!this.specialPagesDir.isDirectory()) { - logger.warn('specialPagesDir {} does not exist or is not a directory; skipping and providing no SpecialPages', this.specialPagesDir) - [] - } else { - def specialPages = [] - this.specialPagesDir.eachFileRecurse(FileType.FILES) { - def type = this.getSpecialPageType(it) - if (type != null) { - def relativePath = this.specialPagesDir.relativePath(it) - def path = stripExtension(relativePath) - logger.info('found specialPage {} with type {}', path, type) - specialPages << new SpecialPage(it.text, path, type) - } else { - logger.warn('ignoring {} since there is no specialPageType for it', it) - } - } - specialPages + protected @Nullable SpecialPage transformFileToT(File file, String relativePath, String extension) { + def specialPageType = getSpecialPageType(extension) + if (!specialPageType) { + logger.warn('there is no SpecialPageType for {}, ignoring', relativePath) } + specialPageType ? new SpecialPage(file.text, relativePath, specialPageType) : null } @Override @@ -61,7 +43,8 @@ class SpecialPageFileSpecialPagesProvider implements SpecialPagesProvider, WithW @Override String toString() { - "SpecialPageFileSpecialPagesProvider(specialPagesDir: ${ this.specialPagesDir }, specialPageTypes: ${ this.specialPageTypes })" + "SpecialPageFileSpecialPagesProvider(specialPagesDir: ${ this.dir }, " + + "specialPageTypes: ${ this.specialPageTypes })" } } diff --git a/lib/src/main/groovy/com/jessebrault/ssg/template/TemplateFileTemplatesProvider.groovy b/lib/src/main/groovy/com/jessebrault/ssg/template/TemplateFileTemplatesProvider.groovy index 09bcc18..1a08903 100644 --- a/lib/src/main/groovy/com/jessebrault/ssg/template/TemplateFileTemplatesProvider.groovy +++ b/lib/src/main/groovy/com/jessebrault/ssg/template/TemplateFileTemplatesProvider.groovy @@ -1,55 +1,38 @@ package com.jessebrault.ssg.template -import com.jessebrault.ssg.provider.WithWatchableDir -import groovy.io.FileType +import com.jessebrault.ssg.provider.AbstractFileCollectionProvider import groovy.transform.EqualsAndHashCode import groovy.transform.NullCheck +import org.jetbrains.annotations.Nullable import org.slf4j.Logger import org.slf4j.LoggerFactory -import static com.jessebrault.ssg.util.ExtensionsUtil.getExtension - @NullCheck @EqualsAndHashCode(includeFields = true) -class TemplateFileTemplatesProvider implements TemplatesProvider, WithWatchableDir { +class TemplateFileTemplatesProvider extends AbstractFileCollectionProvider