Major refactor to renderers.

This commit is contained in:
JesseBrault0709 2023-02-19 21:41:22 +01:00
parent 79c0d158f3
commit c5ac81055c
35 changed files with 680 additions and 923 deletions

View File

@ -25,6 +25,8 @@ dependencies {
// https://mvnrepository.com/artifact/org.slf4j/slf4j-api // https://mvnrepository.com/artifact/org.slf4j/slf4j-api
implementation 'org.slf4j:slf4j-api:1.7.36' implementation 'org.slf4j:slf4j-api:1.7.36'
testFixturesImplementation 'org.slf4j:slf4j-api:1.7.36'
/** /**
* TESTING * TESTING
*/ */

View File

@ -1,7 +1,7 @@
package com.jessebrault.ssg package com.jessebrault.ssg
import com.jessebrault.ssg.buildscript.GroovyBuildScriptRunner import com.jessebrault.ssg.buildscript.GroovyBuildScriptRunner
import com.jessebrault.ssg.output.OutputPage import com.jessebrault.ssg.task.Output
import com.jessebrault.ssg.part.GspPartRenderer import com.jessebrault.ssg.part.GspPartRenderer
import com.jessebrault.ssg.part.PartFilePartsProvider import com.jessebrault.ssg.part.PartFilePartsProvider
import com.jessebrault.ssg.part.PartType import com.jessebrault.ssg.part.PartType
@ -86,10 +86,10 @@ abstract class AbstractBuildCommand extends AbstractSubCommand {
logger.error(it.message) logger.error(it.message)
} }
} else { } else {
result.v2.each { OutputPage outputPage -> result.v2.each { Output output ->
def target = new File(it.outDir, outputPage.path) def target = new File(it.outDir, output.meta.targetPath)
target.createParentDirectories() target.createParentDirectories()
target.write(outputPage.content) target.write(output.content)
} }
} }
} }

View File

@ -1,7 +1,12 @@
package com.jessebrault.ssg package com.jessebrault.ssg
import com.jessebrault.ssg.output.OutputPage import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.specialpage.SpecialPage
import com.jessebrault.ssg.task.Output
import com.jessebrault.ssg.task.OutputMeta
import com.jessebrault.ssg.task.OutputMetaMap
import com.jessebrault.ssg.text.FrontMatter import com.jessebrault.ssg.text.FrontMatter
import com.jessebrault.ssg.text.Text
import groovy.transform.EqualsAndHashCode import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck import groovy.transform.NullCheck
import groovy.transform.TupleConstructor import groovy.transform.TupleConstructor
@ -22,7 +27,7 @@ class SimpleStaticSiteGenerator implements StaticSiteGenerator {
private static final Marker exit = MarkerFactory.getMarker('EXIT') private static final Marker exit = MarkerFactory.getMarker('EXIT')
@Override @Override
Tuple2<Collection<Diagnostic>, Collection<OutputPage>> generate(Build build) { Tuple2<Collection<Diagnostic>, Collection<Output>> generate(Build build) {
logger.trace(enter, 'build: {}', build) logger.trace(enter, 'build: {}', build)
logger.info('processing build with name: {}', build.name) logger.info('processing build with name: {}', build.name)
@ -39,18 +44,29 @@ class SimpleStaticSiteGenerator implements StaticSiteGenerator {
def globals = build.globals def globals = build.globals
Collection<Diagnostic> diagnostics = [] Collection<Diagnostic> diagnostics = []
Collection<OutputPage> generatedPages = [] Collection<Output> generatedPages = []
Map<Text, OutputMeta> textOutputTasks = texts.collectEntries {
[(it): new OutputMeta(it.path, stripExtension(it.path) + '.html')]
}
Map<SpecialPage, OutputMeta> specialPageOutputTasks = specialPages.collectEntries {
[(it): new OutputMeta(it.path, stripExtension(it.path) + '.html')]
}
def textOutputMetas = textOutputTasks.values() as ArrayList
def specialPageOutputMetas = specialPageOutputTasks.values() as ArrayList
def outputMetaMap = new OutputMetaMap([*textOutputMetas, *specialPageOutputMetas])
// Generate pages from each text, but only those that have a 'template' frontMatter field with a valid value // Generate pages from each text, but only those that have a 'template' frontMatter field with a valid value
texts.each { textOutputTasks.each { text, outputMeta ->
logger.trace(enter, 'text: {}', it) logger.trace(enter, 'text: {}, outputMeta: {}', text, outputMeta)
logger.info('processing text: {}', it.path) logger.info('processing text: {}', text.path)
// Extract frontMatter from text // Extract frontMatter from text
def frontMatterResult = it.type.frontMatterGetter.get(it) def frontMatterResult = text.type.frontMatterGetter.get(text)
FrontMatter frontMatter FrontMatter frontMatter
if (frontMatterResult.v1.size() > 0) { if (frontMatterResult.v1.size() > 0) {
logger.debug('diagnostics for getting frontMatter for {}: {}', it.path, frontMatterResult.v1) logger.debug('diagnostics for getting frontMatter for {}: {}', text.path, frontMatterResult.v1)
diagnostics.addAll(frontMatterResult.v1) diagnostics.addAll(frontMatterResult.v1)
logger.trace(exit, '') logger.trace(exit, '')
return return
@ -62,28 +78,30 @@ class SimpleStaticSiteGenerator implements StaticSiteGenerator {
// Find the appropriate template from the frontMatter // Find the appropriate template from the frontMatter
def desiredTemplate = frontMatter.find('template') def desiredTemplate = frontMatter.find('template')
if (desiredTemplate.isEmpty()) { if (desiredTemplate.isEmpty()) {
logger.info('{} has no \'template\' key in its frontMatter; skipping generation', it) logger.info('{} has no \'template\' key in its frontMatter; skipping generation', text)
return return
} }
def template = templates.find { it.path == desiredTemplate.get() } def template = templates.find { it.path == desiredTemplate.get() }
if (template == null) { if (template == null) {
diagnostics << new Diagnostic('in textFile' + it.path + ' frontMatter.template references an unknown template: ' + desiredTemplate, null) diagnostics << new Diagnostic('in textFile' + text.path + ' frontMatter.template references an unknown template: ' + desiredTemplate, null)
logger.trace(exit, '') logger.trace(exit, '')
return return
} }
logger.debug('found template: {}', template) logger.debug('found template: {}', template)
def targetPath = stripExtension(it.path) + '.html'
// Render the template using the result of rendering the text earlier // Render the template using the result of rendering the text earlier
def templateRenderResult = template.type.renderer.render( def templateRenderResult = template.type.renderer.render(
template, template,
frontMatter, text,
it, new RenderContext(
parts, config,
siteSpec, siteSpec,
globals, globals,
targetPath texts,
parts,
outputMeta.sourcePath,
outputMeta.targetPath
)
) )
String renderedTemplate String renderedTemplate
if (templateRenderResult.v1.size() > 0) { if (templateRenderResult.v1.size() > 0) {
@ -95,21 +113,25 @@ class SimpleStaticSiteGenerator implements StaticSiteGenerator {
} }
// Create a GeneratedPage // Create a GeneratedPage
generatedPages << new OutputPage(targetPath, renderedTemplate) generatedPages << new Output(outputMeta, renderedTemplate)
return
} }
// Generate special pages // Generate special pages
specialPages.each { specialPageOutputTasks.each { specialPage, outputMeta ->
logger.info('processing specialPage: {}', it.path) logger.info('processing specialPage: {}', specialPage.path)
def targetPath = stripExtension(it.path) + '.html' def specialPageRenderResult = specialPage.type.renderer.render(
def specialPageRenderResult = it.type.renderer.render( specialPage,
it, new RenderContext(
texts, config,
parts, siteSpec,
siteSpec, globals,
globals, texts,
targetPath parts,
outputMeta.sourcePath,
outputMeta.targetPath
)
) )
String renderedSpecialPage String renderedSpecialPage
if (specialPageRenderResult.v1.size() > 0) { if (specialPageRenderResult.v1.size() > 0) {
@ -121,7 +143,8 @@ class SimpleStaticSiteGenerator implements StaticSiteGenerator {
} }
// Create a GeneratedPage // Create a GeneratedPage
generatedPages << new OutputPage(targetPath, renderedSpecialPage) generatedPages << new Output(outputMeta, renderedSpecialPage)
return
} }
logger.trace(exit, '\n\tdiagnostics: {}\n\tgeneratedPages: {}', diagnostics, generatedPages) logger.trace(exit, '\n\tdiagnostics: {}\n\tgeneratedPages: {}', diagnostics, generatedPages)

View File

@ -1,7 +1,7 @@
package com.jessebrault.ssg package com.jessebrault.ssg
import com.jessebrault.ssg.output.OutputPage import com.jessebrault.ssg.task.Output
interface StaticSiteGenerator { interface StaticSiteGenerator {
Tuple2<Collection<Diagnostic>, Collection<OutputPage>> generate(Build build) Tuple2<Collection<Diagnostic>, Collection<Output>> generate(Build build)
} }

View File

@ -0,0 +1,80 @@
package com.jessebrault.ssg.dsl
import com.jessebrault.ssg.part.EmbeddablePartsMap
import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.text.EmbeddableText
import com.jessebrault.ssg.text.EmbeddableTextsCollection
import com.jessebrault.ssg.text.Text
import com.jessebrault.ssg.url.PathBasedUrlBuilder
import groovy.transform.NullCheck
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.SimpleType
import org.slf4j.LoggerFactory
final class StandardDslMap {
@NullCheck(includeGenerated = true)
static final class Builder {
private final Map custom = [:]
String loggerName = ''
Closure onDiagnostics = { }
Text text = null
void putCustom(key, value) {
this.custom.put(key, value)
}
void putAllCustom(Map m) {
this.custom.putAll(m)
}
}
static Map get(
RenderContext context,
@DelegatesTo(value = Builder, strategy = Closure.DELEGATE_FIRST)
@ClosureParams(
value = SimpleType,
options = ['com.jessebrault.ssg.dsl.StandardDslMap.Builder']
)
Closure builderClosure
) {
def b = new Builder()
builderClosure.resolveStrategy = Closure.DELEGATE_FIRST
builderClosure.delegate = b
builderClosure(b)
[:].tap {
it.globals = context.globals
it.logger = LoggerFactory.getLogger(b.loggerName)
it.parts = new EmbeddablePartsMap(
context.parts,
context,
b.onDiagnostics,
b.text
)
it.siteSpec = context.siteSpec
it.sourcePath = context.sourcePath
it.targetPath = context.targetPath
it.text = b.text ? new EmbeddableText(
b.text,
context.globals,
b.onDiagnostics
) : null
it.texts = new EmbeddableTextsCollection(
context.texts,
context.globals,
b.onDiagnostics
)
it.urlBuilder = new PathBasedUrlBuilder(
context.targetPath,
context.siteSpec.baseUrl
)
it.putAll(b.custom)
}
}
}

View File

@ -1,5 +0,0 @@
package com.jessebrault.ssg.input
interface InputPage {
String getPath()
}

View File

@ -1,31 +0,0 @@
package com.jessebrault.ssg.output
import com.jessebrault.ssg.input.InputPage
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import static com.jessebrault.ssg.util.ExtensionsUtil.stripExtension
@NullCheck
@EqualsAndHashCode
final class OutputPage {
@Deprecated
static OutputPage of(InputPage inputFile, String extension, String content) {
new OutputPage(stripExtension(inputFile.path) + extension, content)
}
final String path
final String content
OutputPage(String path, String content) {
this.path = path
this.content = content
}
@Override
String toString() {
"GeneratedPage(path: ${ this.path })"
}
}

View File

@ -1,39 +1,42 @@
package com.jessebrault.ssg.part package com.jessebrault.ssg.part
import com.jessebrault.ssg.SiteSpec
import com.jessebrault.ssg.text.EmbeddableText import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.text.Text
import groovy.transform.EqualsAndHashCode import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
import org.jetbrains.annotations.Nullable import org.jetbrains.annotations.Nullable
@TupleConstructor(includeFields = true, defaults = false) import static java.util.Objects.requireNonNull
@NullCheck
@EqualsAndHashCode(includeFields = true) @EqualsAndHashCode(includeFields = true)
class EmbeddablePart { class EmbeddablePart {
private final Part part private final Part part
private final SiteSpec siteSpec private final RenderContext context
private final Map globals
private final Closure onDiagnostics private final Closure onDiagnostics
@Nullable @Nullable
private final EmbeddableText text private final Text text
private final Collection<Part> allParts EmbeddablePart(
private final String path Part part,
private final String targetPath RenderContext context,
Closure onDiagnostics,
@Nullable Text text
) {
this.part = requireNonNull(part)
this.context = requireNonNull(context)
this.onDiagnostics = requireNonNull(onDiagnostics)
this.text = text
}
String render(Map binding = [:]) { String render(Map binding = [:]) {
def result = part.type.renderer.render( def result = part.type.renderer.render(
this.part, this.part,
binding, binding,
this.siteSpec, this.context,
this.globals, this.text
this.text,
this.allParts,
this.path,
this.targetPath
) )
if (result.v1.size() > 0) { if (result.v1.size() > 0) {
this.onDiagnostics.call(result.v1) this.onDiagnostics.call(result.v1)
@ -45,8 +48,7 @@ class EmbeddablePart {
@Override @Override
String toString() { String toString() {
"EmbeddablePart(part: ${ this.part }, globals: ${ this.globals }, path: ${ this.path }, " + "EmbeddablePart(part: ${ this.part })"
"targetPath: ${ this.targetPath })"
} }
} }

View File

@ -1,10 +1,12 @@
package com.jessebrault.ssg.part package com.jessebrault.ssg.part
import com.jessebrault.ssg.SiteSpec import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.text.EmbeddableText import com.jessebrault.ssg.text.Text
import groovy.transform.EqualsAndHashCode import groovy.transform.EqualsAndHashCode
import org.jetbrains.annotations.Nullable import org.jetbrains.annotations.Nullable
import static java.util.Objects.requireNonNull
@EqualsAndHashCode(includeFields = true) @EqualsAndHashCode(includeFields = true)
class EmbeddablePartsMap { class EmbeddablePartsMap {
@ -13,21 +15,15 @@ class EmbeddablePartsMap {
EmbeddablePartsMap( EmbeddablePartsMap(
Collection<Part> parts, Collection<Part> parts,
SiteSpec siteSpec, RenderContext context,
Map globals,
Closure onDiagnostics, Closure onDiagnostics,
@Nullable EmbeddableText text = null, @Nullable Text text = null
String path,
String targetPath
) { ) {
Objects.requireNonNull(parts) requireNonNull(parts)
Objects.requireNonNull(siteSpec) requireNonNull(context)
Objects.requireNonNull(globals) requireNonNull(onDiagnostics)
Objects.requireNonNull(onDiagnostics)
Objects.requireNonNull(path)
Objects.requireNonNull(targetPath)
parts.each { parts.each {
this.put(it.path, new EmbeddablePart(it, siteSpec, globals, onDiagnostics, text, parts, path, targetPath)) this.put(it.path, new EmbeddablePart(it, context, onDiagnostics, text))
} }
} }

View File

@ -1,15 +1,14 @@
package com.jessebrault.ssg.part package com.jessebrault.ssg.part
import com.jessebrault.ssg.Diagnostic import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.SiteSpec import com.jessebrault.ssg.dsl.StandardDslMap
import com.jessebrault.ssg.tagbuilder.DynamicTagBuilder import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.text.EmbeddableText import com.jessebrault.ssg.text.EmbeddableText
import com.jessebrault.ssg.url.PathBasedUrlBuilder import com.jessebrault.ssg.text.Text
import groovy.text.GStringTemplateEngine import groovy.text.GStringTemplateEngine
import groovy.text.TemplateEngine import groovy.text.TemplateEngine
import groovy.transform.EqualsAndHashCode import groovy.transform.EqualsAndHashCode
import org.jetbrains.annotations.Nullable import org.jetbrains.annotations.Nullable
import org.slf4j.LoggerFactory
@EqualsAndHashCode @EqualsAndHashCode
class GspPartRenderer implements PartRenderer { class GspPartRenderer implements PartRenderer {
@ -20,46 +19,27 @@ class GspPartRenderer implements PartRenderer {
Tuple2<Collection<Diagnostic>, String> render( Tuple2<Collection<Diagnostic>, String> render(
Part part, Part part,
Map binding, Map binding,
SiteSpec siteSpec, RenderContext context,
Map globals, @Nullable Text text
@Nullable EmbeddableText text = null,
Collection<Part> allParts,
String path,
String targetPath
) { ) {
Objects.requireNonNull(part) Objects.requireNonNull(part)
Objects.requireNonNull(binding) Objects.requireNonNull(binding)
Objects.requireNonNull(siteSpec) Objects.requireNonNull(context)
Objects.requireNonNull(globals) def diagnostics = []
Objects.requireNonNull(allParts)
Objects.requireNonNull(path)
Objects.requireNonNull(targetPath)
def embeddedPartDiagnostics = []
try { try {
def result = engine.createTemplate(part.text).make([ def dslMap = StandardDslMap.get(context) {
binding: binding, it.putCustom('binding', binding)
globals: globals, it.loggerName = "GspPart(${ part.path })"
logger: LoggerFactory.getLogger("Part(${ part.path })"), it.onDiagnostics = diagnostics.&addAll
parts: new EmbeddablePartsMap( if (text) {
allParts, it.text = text
siteSpec, }
globals, }
embeddedPartDiagnostics.&addAll, def result = engine.createTemplate(part.text).make(dslMap)
text, new Tuple2<>(diagnostics, result.toString())
path,
targetPath
),
path: path,
siteSpec: siteSpec,
tagBuilder: new DynamicTagBuilder(),
targetPath: targetPath,
text: text,
urlBuilder: new PathBasedUrlBuilder(targetPath, siteSpec.baseUrl)
])
new Tuple2<>(embeddedPartDiagnostics, result.toString())
} catch (Exception e) { } catch (Exception e) {
new Tuple2<>( new Tuple2<>(
[*embeddedPartDiagnostics, new Diagnostic( [*diagnostics, new Diagnostic(
"An exception occurred while rendering part ${ part.path }:\n${ e }", "An exception occurred while rendering part ${ part.path }:\n${ e }",
e e
)], )],

View File

@ -1,8 +1,8 @@
package com.jessebrault.ssg.part package com.jessebrault.ssg.part
import com.jessebrault.ssg.Diagnostic import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.SiteSpec import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.text.EmbeddableText import com.jessebrault.ssg.text.Text
import org.jetbrains.annotations.Nullable import org.jetbrains.annotations.Nullable
interface PartRenderer { interface PartRenderer {
@ -10,12 +10,8 @@ interface PartRenderer {
Tuple2<Collection<Diagnostic>, String> render( Tuple2<Collection<Diagnostic>, String> render(
Part part, Part part,
Map binding, Map binding,
SiteSpec siteSpec, RenderContext context,
Map globals, @Nullable Text text
@Nullable EmbeddableText text,
Collection<Part> allParts,
String path,
String targetPath
) )
} }

View File

@ -0,0 +1,22 @@
package com.jessebrault.ssg.renderer
import com.jessebrault.ssg.Config
import com.jessebrault.ssg.SiteSpec
import com.jessebrault.ssg.part.Part
import com.jessebrault.ssg.text.Text
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
final class RenderContext {
final Config config
final SiteSpec siteSpec
final Map globals
final Collection<Text> texts
final Collection<Part> parts
final String sourcePath
final String targetPath
}

View File

@ -1,18 +1,12 @@
package com.jessebrault.ssg.specialpage package com.jessebrault.ssg.specialpage
import com.jessebrault.ssg.Diagnostic import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.SiteSpec import com.jessebrault.ssg.dsl.StandardDslMap
import com.jessebrault.ssg.part.EmbeddablePartsMap import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.part.Part
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.GStringTemplateEngine
import groovy.text.TemplateEngine import groovy.text.TemplateEngine
import groovy.transform.EqualsAndHashCode import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck import groovy.transform.NullCheck
import org.slf4j.LoggerFactory
@NullCheck @NullCheck
@EqualsAndHashCode @EqualsAndHashCode
@ -23,35 +17,24 @@ class GspSpecialPageRenderer implements SpecialPageRenderer {
@Override @Override
Tuple2<Collection<Diagnostic>, String> render( Tuple2<Collection<Diagnostic>, String> render(
SpecialPage specialPage, SpecialPage specialPage,
Collection<Text> texts, RenderContext context
Collection<Part> parts,
SiteSpec siteSpec,
Map globals,
String targetPath
) { ) {
Collection<Diagnostic> diagnostics = [] def diagnostics = []
try { try {
def result = engine.createTemplate(specialPage.text).make([ def dslMap = StandardDslMap.get(context) {
globals: globals, it.loggerName = "GspSpecialPage(${ specialPage.path })"
logger: LoggerFactory.getLogger("SpecialPage(${ specialPage.path })"), it.onDiagnostics = diagnostics.&addAll
parts: new EmbeddablePartsMap( }
parts, def result = engine.createTemplate(specialPage.text).make(dslMap)
siteSpec,
globals,
diagnostics.&addAll,
specialPage.path,
targetPath
),
path: specialPage.path,
siteSpec: siteSpec,
tagBuilder: new DynamicTagBuilder(),
targetPath: targetPath,
texts: new EmbeddableTextsCollection(texts, globals, diagnostics.&addAll),
urlBuilder: new PathBasedUrlBuilder(targetPath, siteSpec.baseUrl)
])
new Tuple2<>(diagnostics, result.toString()) new Tuple2<>(diagnostics, result.toString())
} catch (Exception e) { } catch (Exception e) {
new Tuple2<>([*diagnostics, new Diagnostic("An exception occurred while rendering specialPage ${ specialPage.path }:\n${ e }", e)], '') new Tuple2<>(
[*diagnostics, new Diagnostic(
"An exception occurred while rendering specialPage ${ specialPage.path }:\n${ e }",
e
)],
''
)
} }
} }

View File

@ -1,6 +1,6 @@
package com.jessebrault.ssg.specialpage package com.jessebrault.ssg.specialpage
import com.jessebrault.ssg.input.InputPage import com.jessebrault.ssg.task.Input
import groovy.transform.EqualsAndHashCode import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck import groovy.transform.NullCheck
import groovy.transform.TupleConstructor import groovy.transform.TupleConstructor
@ -8,7 +8,7 @@ import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false) @TupleConstructor(defaults = false)
@NullCheck @NullCheck
@EqualsAndHashCode @EqualsAndHashCode
class SpecialPage implements InputPage { class SpecialPage implements Input {
String text String text
String path String path

View File

@ -3,17 +3,14 @@ package com.jessebrault.ssg.specialpage
import com.jessebrault.ssg.Diagnostic import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.SiteSpec import com.jessebrault.ssg.SiteSpec
import com.jessebrault.ssg.part.Part import com.jessebrault.ssg.part.Part
import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.text.Text import com.jessebrault.ssg.text.Text
interface SpecialPageRenderer { interface SpecialPageRenderer {
Tuple2<Collection<Diagnostic>, String> render( Tuple2<Collection<Diagnostic>, String> render(
SpecialPage specialPage, SpecialPage specialPage,
Collection<Text> texts, RenderContext context
Collection <Part> parts,
SiteSpec siteSpec,
Map globals,
String targetPath
) )
} }

View File

@ -0,0 +1,5 @@
package com.jessebrault.ssg.task
interface Input {
String getPath()
}

View File

@ -0,0 +1,20 @@
package com.jessebrault.ssg.task
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck
@EqualsAndHashCode
final class Output {
final OutputMeta meta
final String content
@Override
String toString() {
"Output(meta: ${ this.meta })"
}
}

View File

@ -0,0 +1,20 @@
package com.jessebrault.ssg.task
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck
@EqualsAndHashCode
final class OutputMeta {
final String sourcePath
final String targetPath
@Override
String toString() {
"OutputMeta(sourcePath: ${ sourcePath }, targetPath: ${ targetPath })"
}
}

View File

@ -0,0 +1,19 @@
package com.jessebrault.ssg.task
final class OutputMetaMap {
@Delegate
private final Map<String, OutputMeta> map = [:]
OutputMetaMap(Collection<OutputMeta> outputMetas) {
outputMetas.each {
this.put(it.sourcePath, it)
}
}
@Override
String toString() {
"OutputMetaMap(map: ${ this.map })"
}
}

View File

@ -0,0 +1,6 @@
package com.jessebrault.ssg.task
interface Task {
Output getOutput()
OutputMeta getOutputMeta()
}

View File

@ -0,0 +1,21 @@
package com.jessebrault.ssg.task
import com.jessebrault.ssg.Config
import com.jessebrault.ssg.SiteSpec
import groovy.transform.TupleConstructor
import groovy.transform.builder.Builder
@Builder
@TupleConstructor(defaults = false)
final class TaskContext {
final Config config
final SiteSpec siteSpec
final Collection<OutputMeta> outputMetas
@Override
String toString() {
"TaskContext(config: ${ this.config }, siteSpec: ${ this.siteSpec }, outputMetas: ${ this.outputMetas })"
}
}

View File

@ -0,0 +1,7 @@
package com.jessebrault.ssg.task
import org.jetbrains.annotations.Nullable
interface TaskFactory {
@Nullable Task getTask(Input input, TaskContext context)
}

View File

@ -1,19 +1,13 @@
package com.jessebrault.ssg.template package com.jessebrault.ssg.template
import com.jessebrault.ssg.Diagnostic import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.SiteSpec import com.jessebrault.ssg.dsl.StandardDslMap
import com.jessebrault.ssg.part.EmbeddablePartsMap import com.jessebrault.ssg.renderer.RenderContext
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 import com.jessebrault.ssg.text.Text
import com.jessebrault.ssg.url.PathBasedUrlBuilder
import groovy.text.GStringTemplateEngine import groovy.text.GStringTemplateEngine
import groovy.text.TemplateEngine import groovy.text.TemplateEngine
import groovy.transform.EqualsAndHashCode import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck import groovy.transform.NullCheck
import org.slf4j.LoggerFactory
@NullCheck @NullCheck
@EqualsAndHashCode @EqualsAndHashCode
@ -24,32 +18,26 @@ class GspTemplateRenderer implements TemplateRenderer {
@Override @Override
Tuple2<Collection<Diagnostic>, String> render( Tuple2<Collection<Diagnostic>, String> render(
Template template, Template template,
@Deprecated
FrontMatter frontMatter,
Text text, Text text,
Collection<Part> parts, RenderContext context
SiteSpec siteSpec,
Map globals,
String targetPath
) { ) {
def diagnostics = [] def diagnostics = []
try { try {
def embeddableText = new EmbeddableText(text, globals, diagnostics.&addAll) def dslMap = StandardDslMap.get(context) {
def result = engine.createTemplate(template.text).make([ it.loggerName = "GspTemplate(${ template.path })"
frontMatter: frontMatter, it.onDiagnostics = diagnostics.&addAll
globals: globals, it.text = text
logger: LoggerFactory.getLogger("Template(${ template.path })"), }
parts: new EmbeddablePartsMap(parts, siteSpec, globals, diagnostics.&addAll, embeddableText, text.path, targetPath), def result = engine.createTemplate(template.text).make(dslMap)
path: text.path,
siteSpec: siteSpec,
tagBuilder: new DynamicTagBuilder(),
targetPath: targetPath,
text: embeddableText,
urlBuilder: new PathBasedUrlBuilder(targetPath, siteSpec.baseUrl)
])
new Tuple2<>(diagnostics, result.toString()) new Tuple2<>(diagnostics, result.toString())
} catch (Exception e) { } catch (Exception e) {
new Tuple2<>([*diagnostics, new Diagnostic("An exception occurred while rendering Template ${ template.path }:\n${ e }", e)], '') new Tuple2<>(
[*diagnostics, new Diagnostic(
"An exception occurred while rendering Template ${ template.path }:\n${ e }",
e
)],
''
)
} }
} }

View File

@ -1,25 +1,15 @@
package com.jessebrault.ssg.template package com.jessebrault.ssg.template
import com.jessebrault.ssg.Diagnostic import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.SiteSpec import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.part.Part
import com.jessebrault.ssg.text.FrontMatter
import com.jessebrault.ssg.text.Text import com.jessebrault.ssg.text.Text
interface TemplateRenderer { interface TemplateRenderer {
/**
* TODO: get rid of frontMatter param since we can obtain it from the text
*/
Tuple2<Collection<Diagnostic>, String> render( Tuple2<Collection<Diagnostic>, String> render(
Template template, Template template,
@Deprecated
FrontMatter frontMatter,
Text text, Text text,
Collection<Part> parts, RenderContext context
SiteSpec siteSpec,
Map globals,
String targetPath
) )
} }

View File

@ -6,7 +6,7 @@ import groovy.transform.NullCheck
import groovy.transform.TupleConstructor import groovy.transform.TupleConstructor
@TupleConstructor(includeFields = true, defaults = false) @TupleConstructor(includeFields = true, defaults = false)
@NullCheck @NullCheck(includeGenerated = true)
@EqualsAndHashCode(includeFields = true) @EqualsAndHashCode(includeFields = true)
class EmbeddableText { class EmbeddableText {

View File

@ -1,6 +1,6 @@
package com.jessebrault.ssg.text package com.jessebrault.ssg.text
import com.jessebrault.ssg.input.InputPage import com.jessebrault.ssg.task.Input
import groovy.transform.EqualsAndHashCode import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck import groovy.transform.NullCheck
import groovy.transform.TupleConstructor import groovy.transform.TupleConstructor
@ -8,7 +8,7 @@ import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false) @TupleConstructor(defaults = false)
@NullCheck @NullCheck
@EqualsAndHashCode @EqualsAndHashCode
class Text implements InputPage { class Text implements Input {
String text String text
String path String path

View File

@ -71,7 +71,7 @@ class SimpleStaticSiteGeneratorIntegrationTests {
assertTrue(result.v2.size() == 1) assertTrue(result.v2.size() == 1)
def p0 = result.v2[0] def p0 = result.v2[0]
assertEquals('test.html', p0.path) assertEquals('test.html', p0.meta.targetPath)
assertEquals('<p><strong>Hello, World!</strong></p>\n', p0.content) assertEquals('<p><strong>Hello, World!</strong></p>\n', p0.content)
} }
@ -91,7 +91,7 @@ class SimpleStaticSiteGeneratorIntegrationTests {
assertTrue(result.v2.size() == 1) assertTrue(result.v2.size() == 1)
def p0 = result.v2[0] def p0 = result.v2[0]
assertEquals('nested/nested.html', p0.path) assertEquals('nested/nested.html', p0.meta.targetPath)
assertEquals('<p><strong>Hello, World!</strong></p>\n', p0.content) assertEquals('<p><strong>Hello, World!</strong></p>\n', p0.content)
} }
@ -107,11 +107,11 @@ class SimpleStaticSiteGeneratorIntegrationTests {
assertEmptyDiagnostics(result) assertEmptyDiagnostics(result)
assertEquals(2, result.v2.size()) assertEquals(2, result.v2.size())
def testPage = result.v2.find { it.path == 'test.html' } def testPage = result.v2.find { it.meta.targetPath == 'test.html' }
assertNotNull(testPage) assertNotNull(testPage)
assertEquals('2', testPage.content) assertEquals('2', testPage.content)
def specialPage = result.v2.find { it.path == 'special.html' } def specialPage = result.v2.find { it.meta.targetPath == 'special.html' }
assertNotNull(specialPage) assertNotNull(specialPage)
assertEquals('<p>Hello, World!</p>\n', specialPage.content) assertEquals('<p>Hello, World!</p>\n', specialPage.content)
} }

View File

@ -1,224 +1,58 @@
package com.jessebrault.ssg.part package com.jessebrault.ssg.part
import com.jessebrault.ssg.SiteSpec import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.text.EmbeddableText import com.jessebrault.ssg.dsl.StandardDslConsumerTests
import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.text.Text
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.Mock
import org.mockito.MockedStatic
import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoExtension
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import static com.jessebrault.ssg.testutil.DiagnosticsUtil.assertDiagnosticException import static com.jessebrault.ssg.testutil.DiagnosticsUtil.assertDiagnosticException
import static com.jessebrault.ssg.testutil.DiagnosticsUtil.assertEmptyDiagnostics import static com.jessebrault.ssg.testutil.RenderContextUtil.getRenderContext
import static com.jessebrault.ssg.testutil.DiagnosticsUtil.getDiagnosticsMessageSupplier
import static com.jessebrault.ssg.text.TextMocks.renderableText import static com.jessebrault.ssg.text.TextMocks.renderableText
import static org.junit.jupiter.api.Assertions.assertEquals import static org.junit.jupiter.api.Assertions.assertEquals
import static org.junit.jupiter.api.Assertions.assertInstanceOf
import static org.junit.jupiter.api.Assertions.assertTrue
import static org.mockito.ArgumentMatchers.anyString
import static org.mockito.Mockito.mockStatic
import static org.mockito.Mockito.verify
@ExtendWith(MockitoExtension) @ExtendWith(MockitoExtension)
class GspPartRendererTests { class GspPartRendererTests implements StandardDslConsumerTests {
private final PartRenderer renderer = new GspPartRenderer() private final PartRenderer renderer = new GspPartRenderer()
@Test private Tuple2<Collection<Diagnostic>, String> doRender(
void rendersWithNoBindingOrGlobals() { String scriptlet,
def part = new Part('', null, 'Hello, World!') RenderContext context,
def r = this.renderer.render( Map binding = [:],
part, Text text = null
[:], ) {
new SiteSpec('', ''), this.renderer.render(
[:], new Part('', new PartType([], this.renderer), scriptlet),
null, binding,
[part], context,
'', text
''
) )
assertTrue(r.v1.size() == 0) }
assertEquals('Hello, World!', r.v2)
@Override
Tuple2<Collection<Diagnostic>, String> render(String scriptlet, RenderContext context) {
this.doRender(scriptlet, context)
} }
@Test @Test
void rendersWithBinding() { void rendersWithBinding() {
def part = new Part('', null, "<%= binding['greeting'] %>") this.checkResult(
def r = this.renderer.render( 'Hello, World!',
part, this.doRender('<%= binding.greeting %>', getRenderContext(), [greeting: 'Hello, World!'])
[greeting: 'Hello, World!'],
new SiteSpec('', ''),
[:],
null,
[part],
'',
''
) )
assertTrue(r.v1.size() == 0)
assertEquals('Hello, World!', r.v2)
}
@Test
void rendersWithGlobals() {
def part = new Part(null, null, "<%= globals['greeting'] %>")
def r = this.renderer.render(
part,
[:],
new SiteSpec('', ''),
[greeting: 'Hello, World!'],
null,
[part],
'',
''
)
assertTrue(r.v1.size() == 0)
assertEquals('Hello, World!', r.v2)
} }
@Test @Test
void textAvailable() { void textAvailable() {
def part = new Part('', null, '<%= text.render() %>') this.checkResult('Hello, World!', this.renderer.render(
def textDiagnostics = [] new Part('', new PartType([], this.renderer), '<%= text.render() %>'),
def r = this.renderer.render(
part,
[:], [:],
new SiteSpec('', ''), getRenderContext(),
[:], renderableText('Hello, World!')
new EmbeddableText(renderableText('Hello, World!'), [:], textDiagnostics.&addAll), ))
[part],
'',
''
)
assertTrue(textDiagnostics.isEmpty())
assertTrue(r.v1.isEmpty())
assertEquals('Hello, World!', r.v2)
}
@Test
void tagBuilderAvailable() {
def part = new Part('', null, '<%= tagBuilder.test() %>')
def r = this.renderer.render(
part,
[:],
new SiteSpec('', ''),
[:],
null,
[part],
'',
''
)
assertTrue(r.v1.isEmpty())
assertEquals('<test />', 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,
[:],
new SiteSpec('', ''),
[:],
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,
[:],
new SiteSpec('', ''),
[:],
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,
[:],
new SiteSpec('', ''),
[:],
null,
[part],
'',
'test/test.html'
)
assertEmptyDiagnostics(r)
assertEquals('../images/test.jpg', r.v2)
}
@Test
void targetPathAvailable() {
def part = new Part('', null, '<%= targetPath %>')
def r = this.renderer.render(
part,
[:],
new SiteSpec('', ''),
[:],
null,
[part],
'',
'test/test.html'
)
assertEmptyDiagnostics(r)
assertEquals('test/test.html', r.v2)
}
@Test
void siteSpecBaseUrlAvailable() {
def part = new Part('', null, '<%= siteSpec.baseUrl %>')
def r = this.renderer.render(
part,
[:],
new SiteSpec('', 'https://test.com'),
[:],
null,
[part],
'',
''
)
assertEmptyDiagnostics(r)
assertEquals('https://test.com', r.v2)
}
@Test
void loggerAvailable(@Mock Logger logger) {
try (MockedStatic<LoggerFactory> loggerFactory = mockStatic(LoggerFactory)) {
loggerFactory.when { LoggerFactory.getLogger(anyString()) }
.thenReturn(logger)
def part = new Part('', null, '<% logger.info "Hello, World!" %>')
def r = this.renderer.render(
part,
[:],
new SiteSpec('', ''),
[:],
null,
[part],
'',
''
)
assertEmptyDiagnostics(r)
verify(logger).info('Hello, World!')
}
} }
@Test @Test
@ -228,16 +62,16 @@ class GspPartRendererTests {
new PartType([], this.renderer), new PartType([], this.renderer),
'<% throw new RuntimeException() %>' '<% throw new RuntimeException() %>'
) )
def callerPart = new Part('caller.gsp', null, '<% parts["nestedProblem.gsp"].render() %>') def callerPart = new Part(
'caller.gsp',
null,
'<% parts["nestedProblem.gsp"].render() %>'
)
def r = this.renderer.render( def r = this.renderer.render(
callerPart, callerPart,
[:], [:],
new SiteSpec('', ''), getRenderContext(parts: [callerPart, nestedProblemPart]),
[:], null
null,
[callerPart, nestedProblemPart],
'',
''
) )
assertEquals(1, r.v1.size()) assertEquals(1, r.v1.size())
assertDiagnosticException(RuntimeException, r.v1[0]) assertDiagnosticException(RuntimeException, r.v1[0])
@ -249,7 +83,7 @@ class GspPartRendererTests {
def nestedProblemPart = new Part( def nestedProblemPart = new Part(
'nestedProblem.gsp', 'nestedProblem.gsp',
new PartType([], this.renderer), new PartType([], this.renderer),
'<% throw new RuntimeException() %>' '<% throw new RuntimeException("nested problem exception") %>'
) )
def callerPart = new Part( def callerPart = new Part(
'caller.gsp', 'caller.gsp',
@ -259,15 +93,17 @@ class GspPartRendererTests {
def r = this.renderer.render( def r = this.renderer.render(
callerPart, callerPart,
[:], [:],
new SiteSpec('', ''), getRenderContext(parts: [callerPart, nestedProblemPart]),
[:], null
null,
[callerPart, nestedProblemPart],
'',
''
) )
assertEquals(1, r.v1.size()) assertEquals(1, r.v1.size())
assertDiagnosticException(RuntimeException, r.v1[0]) assertDiagnosticException(RuntimeException, r.v1[0]) { e ->
assertEquals('nested problem exception', e.message, {
def w = new StringWriter()
e.printStackTrace(new PrintWriter(w))
w.toString()
})
}
assertEquals('Hello, World!', r.v2) assertEquals('Hello, World!', r.v2)
} }

View File

@ -1,242 +1,22 @@
package com.jessebrault.ssg.specialpage package com.jessebrault.ssg.specialpage
import com.jessebrault.ssg.SiteSpec import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.part.Part import com.jessebrault.ssg.dsl.StandardDslConsumerTests
import com.jessebrault.ssg.part.PartRenderer import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.part.PartType
import com.jessebrault.ssg.text.*
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.Mock
import org.mockito.MockedStatic
import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoExtension
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import static com.jessebrault.ssg.testutil.DiagnosticsUtil.assertEmptyDiagnostics
import static org.junit.jupiter.api.Assertions.assertEquals
import static org.mockito.ArgumentMatchers.any
import static org.mockito.ArgumentMatchers.anyString
import static org.mockito.ArgumentMatchers.argThat
import static org.mockito.Mockito.mockStatic
import static org.mockito.Mockito.verify
import static org.mockito.Mockito.when
@ExtendWith(MockitoExtension) @ExtendWith(MockitoExtension)
class GspSpecialPageRendererTests { class GspSpecialPageRendererTests implements StandardDslConsumerTests {
private final SpecialPageRenderer renderer = new GspSpecialPageRenderer() private final SpecialPageRenderer renderer = new GspSpecialPageRenderer()
@Test @Override
void rendersGlobal() { Tuple2<Collection<Diagnostic>, String> render(String scriptlet, RenderContext context) {
def specialPage = new SpecialPage("<%= globals['greeting'] %>", 'test.gsp', null) this.renderer.render(
def globals = [greeting: 'Hello, World!'] new SpecialPage(scriptlet, '', new SpecialPageType([], this.renderer)),
def r = this.renderer.render( context
specialPage,
[],
[],
new SiteSpec('', ''),
globals,
''
) )
assertEmptyDiagnostics(r)
assertEquals('Hello, World!', r.v2)
}
@Test
void rendersPartWithNoBinding(@Mock PartRenderer partRenderer) {
when(partRenderer.render(any(), any(), 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() %>", 'test.gsp', null)
def r = this.renderer.render(
specialPage,
[],
[part],
new SiteSpec('', ''),
[:],
''
)
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(),
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!'])",
'test.gsp',
null
)
def r = this.renderer.render(
specialPage,
[],
[part],
new SiteSpec('', ''),
[:],
''
)
assertEmptyDiagnostics(r)
assertEquals('Hello, World!', r.v2)
}
@Test
void rendersText(@Mock TextRenderer textRenderer, @Mock FrontMatterGetter frontMatterGetter) {
when(textRenderer.render(any(), any()))
.thenReturn(new Tuple2<>([], '<p><strong>Hello, World!</strong></p>\n'))
def textType = new TextType([], textRenderer, frontMatterGetter, new MarkdownExcerptGetter())
def text = new Text('', 'test', textType)
def specialPage = new SpecialPage(
"<%= texts.find { it.path == 'test' }.render() %>",
'test.gsp',
null
)
def r = this.renderer.render(
specialPage,
[text],
[],
new SiteSpec('', ''),
[:],
''
)
assertEmptyDiagnostics(r)
assertEquals('<p><strong>Hello, World!</strong></p>\n', r.v2)
}
@Test
void tagBuilderAvailable() {
def specialPage = new SpecialPage('<%= tagBuilder.test() %>', 'test.gsp', null)
def r = this.renderer.render(
specialPage,
[],
[],
new SiteSpec('', ''),
[:],
''
)
assertEmptyDiagnostics(r)
assertEquals('<test />', r.v2)
}
@Test
void pathAvailable() {
def specialPage = new SpecialPage('<%= path %>', 'test.gsp', null)
def r = this.renderer.render(
specialPage,
[],
[],
new SiteSpec('', ''),
[:],
''
)
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,
[],
[],
new SiteSpec('', ''),
[:],
'test.html'
)
assertEmptyDiagnostics(r)
assertEquals('images/test.jpg', r.v2)
}
@Test
void urlBuilderIsRelativeToTargetPath() {
def specialPage = new SpecialPage(
'<%= urlBuilder.relative("images/test.jpg") %>',
'',
null
)
def r = this.renderer.render(
specialPage,
[],
[],
new SiteSpec('', ''),
[:],
'test/test.html'
)
assertEmptyDiagnostics(r)
assertEquals('../images/test.jpg', r.v2)
}
@Test
void targetPathAvailable() {
def specialPage = new SpecialPage(
'<%= targetPath %>',
'',
null
)
def r = this.renderer.render(
specialPage,
[],
[],
new SiteSpec('', ''),
[:],
'test.html'
)
assertEmptyDiagnostics(r)
assertEquals('test.html', r.v2)
}
@Test
void siteSpecBaseUrlAvailable() {
def specialPage = new SpecialPage(
'<%= siteSpec.baseUrl %>',
'',
null
)
def r = this.renderer.render(
specialPage,
[],
[],
new SiteSpec('', 'https://test.com'),
[:],
''
)
assertEmptyDiagnostics(r)
assertEquals('https://test.com', r.v2)
}
@Test
void loggerAvailable(@Mock Logger logger) {
try (MockedStatic<LoggerFactory> loggerFactory = mockStatic(LoggerFactory)) {
loggerFactory.when { LoggerFactory.getLogger(anyString()) }
.thenReturn(logger)
def specialPage = new SpecialPage('<% logger.info "Hello, World!" %>', '', null)
def r = this.renderer.render(
specialPage, [], [], new SiteSpec('', ''), [:], ''
)
assertEmptyDiagnostics(r)
verify(logger).info('Hello, World!')
}
} }
} }

View File

@ -1,274 +1,47 @@
package com.jessebrault.ssg.template package com.jessebrault.ssg.template
import com.jessebrault.ssg.SiteSpec import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.part.Part import com.jessebrault.ssg.dsl.StandardDslConsumerTests
import com.jessebrault.ssg.part.PartRenderer import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.part.PartType import com.jessebrault.ssg.text.Text
import com.jessebrault.ssg.text.FrontMatter
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.Mock
import org.mockito.MockedStatic
import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoExtension
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import static com.jessebrault.ssg.testutil.DiagnosticsUtil.assertEmptyDiagnostics
import static com.jessebrault.ssg.testutil.DiagnosticsUtil.getDiagnosticsMessageSupplier import static com.jessebrault.ssg.testutil.DiagnosticsUtil.getDiagnosticsMessageSupplier
import static com.jessebrault.ssg.text.TextMocks.* import static com.jessebrault.ssg.testutil.RenderContextUtil.getRenderContext
import static com.jessebrault.ssg.text.TextMocks.blankText
import static com.jessebrault.ssg.text.TextMocks.renderableText
import static org.junit.jupiter.api.Assertions.assertEquals import static org.junit.jupiter.api.Assertions.assertEquals
import static org.junit.jupiter.api.Assertions.assertTrue import static org.junit.jupiter.api.Assertions.assertTrue
import static org.mockito.ArgumentMatchers.any
import static org.mockito.ArgumentMatchers.anyString
import static org.mockito.ArgumentMatchers.argThat
import static org.mockito.Mockito.mockStatic
import static org.mockito.Mockito.verify
import static org.mockito.Mockito.when
@ExtendWith(MockitoExtension) @ExtendWith(MockitoExtension)
class GspTemplateRendererTests { class GspTemplateRendererTests implements StandardDslConsumerTests {
private final TemplateRenderer renderer = new GspTemplateRenderer() private final TemplateRenderer renderer = new GspTemplateRenderer()
@Test private Tuple2<Collection<Diagnostic>, String> doRender(String scriptlet, Text text, RenderContext context) {
void rendersPartWithNoBinding(@Mock PartRenderer partRenderer) { this.renderer.render(new Template(scriptlet, '', null), text, context)
def template = new Template(
"<%= parts['test'].render() %>",
null,
null
)
when(partRenderer.render(any(), any(), 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, [:]),
blankText(),
[part],
new SiteSpec('', ''),
[:],
''
)
assertTrue(r.v1.isEmpty(), getDiagnosticsMessageSupplier(r.v1))
assertEquals('Hello, World!', r.v2)
} }
@Test @Override
void rendersPartWithBinding(@Mock PartRenderer partRenderer) { Tuple2<Collection<Diagnostic>, String> render(String scriptlet, RenderContext context) {
def template = new Template( this.doRender(scriptlet, blankText(), context)
"<%= parts['greeting'].render([person: 'World']) %>",
null,
null
)
when(partRenderer.render(
any(),
argThat { Map m -> m.get('person') == 'World' },
any(),
any(),
any(),
any(),
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, [:]),
blankText(),
[part],
new SiteSpec('', ''),
[:],
''
)
assertEmptyDiagnostics(r)
assertEquals('Hello, World!', r.v2)
}
@Test
void rendersFrontMatter() {
def template = new Template("<%= frontMatter['title'] %>", null, null)
def r = this.renderer.render(
template,
new FrontMatter(null, [title: ['Hello!']]),
blankText(),
[],
new SiteSpec('', ''),
[:],
''
)
assertEmptyDiagnostics(r)
assertEquals('Hello!', r.v2)
}
@Test
void rendersGlobal() {
def template = new Template("<%= globals['test'] %>", null, null)
def r = this.renderer.render(
template,
new FrontMatter(null, [:]),
blankText(),
[],
new SiteSpec('', ''),
[test: 'Hello, World!'],
''
)
assertEmptyDiagnostics(r)
assertEquals('Hello, World!', r.v2)
} }
/**
* TODO: refactor this and the super interface methods so that we can re-use rendering logic
*/
@Test @Test
void textAvailableToRender() { void textAvailableToRender() {
def template = new Template('<%= text.render() %>', null, null) def template = new Template('<%= text.render() %>', null, null)
def r = this.renderer.render( def r = this.renderer.render(
template, template,
new FrontMatter(null, [:]),
renderableText('Hello, World!'), renderableText('Hello, World!'),
[], getRenderContext()
new SiteSpec('', ''),
[:],
''
) )
assertTrue(r.v1.isEmpty(), getDiagnosticsMessageSupplier(r.v1)) assertTrue(r.v1.isEmpty(), getDiagnosticsMessageSupplier(r.v1))
assertEquals('Hello, World!', r.v2) 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, [:]),
blankText(),
[],
new SiteSpec('', ''),
[:],
''
)
assertTrue(r.v1.isEmpty(), getDiagnosticsMessageSupplier(r.v1))
assertEquals('<test />', r.v2)
}
@Test
void pathAvailable() {
def template = new Template('<%= path %>', null, null)
def r = this.renderer.render(
template,
new FrontMatter(null, [:]),
textWithPath('test.md'),
[],
new SiteSpec('', ''),
[:],
''
)
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, [:]),
blankText(),
[],
new SiteSpec('', ''),
[:],
'test.html'
)
assertTrue(r.v1.isEmpty(), getDiagnosticsMessageSupplier(r.v1))
assertEquals('images/test.jpg', r.v2)
}
@Test
void urlBuilderIsRelativeToTargetPath() {
def template = new Template('<%= urlBuilder.relative("images/test.jpg") %>', null, null)
def r = this.renderer.render(
template,
new FrontMatter(null, [:]),
blankText(),
[],
new SiteSpec('', ''),
[:],
'test/test.html'
)
assertEmptyDiagnostics(r)
assertEquals('../images/test.jpg', r.v2)
}
@Test
void urlBuilderAbsolutePathCorrect() {
def template = new Template('<%= urlBuilder.absolute %>', null, null)
def r = this.renderer.render(
template,
new FrontMatter(null, [:]),
blankText(),
[],
new SiteSpec('', 'https://test.com'),
[:],
'test/test.html'
)
assertEmptyDiagnostics(r)
assertEquals('https://test.com/test/test.html', r.v2)
}
@Test
void targetPathAvailable() {
def template = new Template('<%= targetPath %>', null, null)
def r = this.renderer.render(
template,
new FrontMatter(null, [:]),
blankText(),
[],
new SiteSpec('', ''),
[:],
'test.html'
)
assertEmptyDiagnostics(r)
assertEquals('test.html', r.v2)
}
@Test
void siteSpecBaseUrlAvailable() {
def template = new Template('<%= siteSpec.baseUrl %>', null, null)
def r = this.renderer.render(
template,
new FrontMatter(null, [:]),
blankText(),
[],
new SiteSpec('', 'https://test.com'),
[:],
'test.html'
)
assertEmptyDiagnostics(r)
assertEquals('https://test.com', r.v2)
}
@Test
void loggerAvailable(@Mock Logger logger) {
try (MockedStatic<LoggerFactory> loggerFactory = mockStatic(LoggerFactory)) {
loggerFactory.when(() -> LoggerFactory.getLogger(anyString()))
.thenReturn(logger)
def template = new Template('<% logger.info "Hello, World!" %>', null, null)
def r = this.renderer.render(
template,
new FrontMatter(null, [:]),
blankText(),
[],
new SiteSpec('', ''),
[:],
''
)
assertEmptyDiagnostics(r)
verify(logger).info('Hello, World!')
}
}
} }

View File

@ -0,0 +1,28 @@
package com.jessebrault.ssg.dsl
interface DslScriptletProvider {
String getGlobalsAvailable()
String getRenderGlobal()
String getLoggerAvailable()
String getPartsAvailable()
String getRenderPartFromParts()
String getSiteSpecAvailable()
String getRenderSiteSpecValues()
String getSourcePathAvailable()
String getRenderSourcePathValue()
String getTagBuilderAvailable()
String getTargetPathAvailable()
String getRenderTargetPath()
String getTextsAvailable()
String getRenderTextFromTexts()
String getUrlBuilderAvailable()
String getUrlBuilderCorrectlyConfigured()
}

View File

@ -0,0 +1,180 @@
package com.jessebrault.ssg.dsl
import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.SiteSpec
import com.jessebrault.ssg.part.EmbeddablePartsMap
import com.jessebrault.ssg.part.Part
import com.jessebrault.ssg.part.PartRenderer
import com.jessebrault.ssg.part.PartType
import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.tagbuilder.TagBuilder
import com.jessebrault.ssg.text.EmbeddableTextsCollection
import com.jessebrault.ssg.url.UrlBuilder
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.Mock
import org.mockito.MockedStatic
import org.mockito.junit.jupiter.MockitoExtension
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import static com.jessebrault.ssg.testutil.DiagnosticsUtil.assertEmptyDiagnostics
import static com.jessebrault.ssg.testutil.RenderContextUtil.getRenderContext
import static com.jessebrault.ssg.text.TextMocks.renderableTextWithPath
import static org.junit.jupiter.api.Assertions.assertEquals
import static org.junit.jupiter.api.Assertions.fail
import static org.mockito.ArgumentMatchers.any
import static org.mockito.ArgumentMatchers.anyString
import static org.mockito.Mockito.*
@ExtendWith(MockitoExtension)
interface StandardDslConsumerTests {
Tuple2<Collection<Diagnostic>, String> render(String scriptlet, RenderContext context)
default void checkResult(String expected, Tuple2<Collection<Diagnostic>, String> result) {
assertEmptyDiagnostics(result)
assertEquals(expected, result.v2)
}
default void doDslRenderTest(String expected, String scriptlet, RenderContext context = null) {
this.checkResult(expected, this.render(scriptlet, context ?: getRenderContext()))
}
default void doDslAssertionTest(String scriptlet, RenderContext context = null) {
try {
this.render(scriptlet, context ?: getRenderContext())
} catch (AssertionError e) {
fail(e)
}
}
@Test
default void rendersGlobal() {
this.doDslRenderTest(
'Hello, World!',
'<%= globals.test %>',
getRenderContext(globals: [test: 'Hello, World!'])
)
}
@Test
default void loggerAvailable(@Mock Logger logger) {
try (MockedStatic<LoggerFactory> loggerFactory = mockStatic(LoggerFactory)) {
loggerFactory.when { LoggerFactory.getLogger(anyString()) }
.thenReturn(logger)
this.doDslAssertionTest('<% assert logger; logger.info("Hello, World!") %>')
verify(logger).info('Hello, World!')
}
}
@Test
default void partsAvailable() {
this.doDslAssertionTest("<% assert parts != null && parts instanceof ${ EmbeddablePartsMap.name } %>")
}
@Test
default void partAvailableAndRenderable(@Mock PartRenderer partRenderer) {
when(partRenderer.render(any(), any(), any(), any()))
.thenReturn(new Tuple2<>([], 'Hello, World!'))
def part = new Part(
'test.gsp',
new PartType([], partRenderer),
'Hello, World!'
)
this.doDslRenderTest(
'Hello, World!',
'<%= parts["test.gsp"].render() %>',
getRenderContext(parts: [part])
)
}
@Test
default void siteSpecAvailable() {
this.doDslAssertionTest(
"<% assert siteSpec && siteSpec instanceof ${ SiteSpec.name } %>"
)
}
@Test
default void siteSpecRendersCorrectValues() {
def siteSpec = new SiteSpec('Test Site', 'https://test.com')
this.doDslRenderTest(
'Test Site https://test.com',
'<%= siteSpec.name + " " + siteSpec.baseUrl %>',
getRenderContext(siteSpec: siteSpec)
)
}
@Test
default void sourcePathAvailable() {
this.doDslAssertionTest(
'<% assert sourcePath && sourcePath instanceof String %>',
getRenderContext(sourcePath: 'test.md')
)
}
@Test
default void sourcePathRendersCorrectValue() {
this.doDslRenderTest(
'test.md',
'<%= sourcePath %>',
getRenderContext(sourcePath: 'test.md')
)
}
@Test
default void tagBuilderAvailable() {
this.doDslAssertionTest("<% assert tagBuider && tagBuilder instanceof ${ TagBuilder.name } %>")
}
@Test
default void targetPathAvailable() {
this.doDslAssertionTest(
'<% assert targetPath && targetPath instanceof String %>',
getRenderContext(targetPath: 'test/test.html')
)
}
@Test
default void targetPathRendersCorrectValue() {
this.doDslRenderTest(
'test/test.html',
'<%= targetPath %>',
getRenderContext(targetPath: 'test/test.html')
)
}
@Test
default void textsAvailable() {
this.doDslAssertionTest(
"<% assert texts != null && texts instanceof ${ EmbeddableTextsCollection.name } %>"
)
}
@Test
default void textsTextAvailableAndRenderable() {
def testText = renderableTextWithPath('Hello, World!', 'test.md')
this.doDslRenderTest(
'Hello, World!',
'<%= texts.find { it.path == "test.md" }.render() %>',
getRenderContext(texts: [testText])
)
}
@Test
default void urlBuilderAvailable() {
this.doDslAssertionTest("<% assert urlBuilder && urlBuilder instanceof ${ UrlBuilder.name } %>")
}
@Test
default void urlBuilderCorrectlyConfigured() {
this.doDslRenderTest(
'../images/test.jpg',
'<%= urlBuilder.relative("images/test.jpg") %>',
getRenderContext(targetPath: 'test/test.html')
)
}
}

View File

@ -2,6 +2,8 @@ package com.jessebrault.ssg.testutil
import com.jessebrault.ssg.Diagnostic import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.text.ExcerptGetter import com.jessebrault.ssg.text.ExcerptGetter
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.FirstParam
import static org.junit.jupiter.api.Assertions.assertInstanceOf import static org.junit.jupiter.api.Assertions.assertInstanceOf
import static org.junit.jupiter.api.Assertions.assertTrue import static org.junit.jupiter.api.Assertions.assertTrue
@ -23,10 +25,18 @@ class DiagnosticsUtil {
assertTrue(result.v1.isEmpty(), getDiagnosticsMessageSupplier(result.v1)) assertTrue(result.v1.isEmpty(), getDiagnosticsMessageSupplier(result.v1))
} }
static void assertDiagnosticException(Class<? extends Exception> expectedException, Diagnostic diagnostic) { static <E extends Exception> void assertDiagnosticException(
Class<E> expectedException,
Diagnostic diagnostic,
@ClosureParams(FirstParam.FirstGenericType)
Closure<Void> additionalAssertions = null
) {
assertInstanceOf(expectedException, diagnostic.exception, { assertInstanceOf(expectedException, diagnostic.exception, {
"Incorrect diagnostic exception class; message: ${ diagnostic.message }" "Incorrect diagnostic exception class; message: ${ diagnostic.message }"
}) })
if (additionalAssertions) {
additionalAssertions(diagnostic.exception)
}
} }
} }

View File

@ -0,0 +1,21 @@
package com.jessebrault.ssg.testutil
import com.jessebrault.ssg.Config
import com.jessebrault.ssg.SiteSpec
import com.jessebrault.ssg.renderer.RenderContext
class RenderContextUtil {
static RenderContext getRenderContext(Map args = null) {
new RenderContext(
args?.config as Config ?: new Config(),
args?.siteSpec as SiteSpec ?: new SiteSpec('', ''),
args?.globals as Map ?: [:],
args?.texts as Collection ?: [],
args?.parts as Collection ?: [],
args?.sourcePath as String ?: '',
args?.targetPath as String ?: ''
)
}
}

View File

@ -28,4 +28,12 @@ class TextMocks {
new Text('', path, new TextType([], textRenderer, frontMatterGetter, excerptGetter)) new Text('', path, new TextType([], textRenderer, frontMatterGetter, excerptGetter))
} }
static Text renderableTextWithPath(String text, String path) {
def textRenderer = mock(TextRenderer)
when(textRenderer.render(any(), any())).thenReturn(new Tuple2<>([], text))
def frontMatterGetter = mock(FrontMatterGetter)
def excerptGetter = mock(ExcerptGetter)
new Text('', path, new TextType([], textRenderer, frontMatterGetter, excerptGetter))
}
} }