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
implementation 'org.slf4j:slf4j-api:1.7.36'
testFixturesImplementation 'org.slf4j:slf4j-api:1.7.36'
/**
* TESTING
*/

View File

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

View File

@ -1,7 +1,12 @@
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.Text
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@ -22,7 +27,7 @@ class SimpleStaticSiteGenerator implements StaticSiteGenerator {
private static final Marker exit = MarkerFactory.getMarker('EXIT')
@Override
Tuple2<Collection<Diagnostic>, Collection<OutputPage>> generate(Build build) {
Tuple2<Collection<Diagnostic>, Collection<Output>> generate(Build build) {
logger.trace(enter, 'build: {}', build)
logger.info('processing build with name: {}', build.name)
@ -39,18 +44,29 @@ class SimpleStaticSiteGenerator implements StaticSiteGenerator {
def globals = build.globals
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
texts.each {
logger.trace(enter, 'text: {}', it)
logger.info('processing text: {}', it.path)
textOutputTasks.each { text, outputMeta ->
logger.trace(enter, 'text: {}, outputMeta: {}', text, outputMeta)
logger.info('processing text: {}', text.path)
// Extract frontMatter from text
def frontMatterResult = it.type.frontMatterGetter.get(it)
def frontMatterResult = text.type.frontMatterGetter.get(text)
FrontMatter frontMatter
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)
logger.trace(exit, '')
return
@ -62,28 +78,30 @@ class SimpleStaticSiteGenerator implements StaticSiteGenerator {
// Find the appropriate template from the frontMatter
def desiredTemplate = frontMatter.find('template')
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
}
def template = templates.find { it.path == desiredTemplate.get() }
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, '')
return
}
logger.debug('found template: {}', template)
def targetPath = stripExtension(it.path) + '.html'
// Render the template using the result of rendering the text earlier
def templateRenderResult = template.type.renderer.render(
template,
frontMatter,
it,
parts,
siteSpec,
globals,
targetPath
text,
new RenderContext(
config,
siteSpec,
globals,
texts,
parts,
outputMeta.sourcePath,
outputMeta.targetPath
)
)
String renderedTemplate
if (templateRenderResult.v1.size() > 0) {
@ -95,21 +113,25 @@ class SimpleStaticSiteGenerator implements StaticSiteGenerator {
}
// Create a GeneratedPage
generatedPages << new OutputPage(targetPath, renderedTemplate)
generatedPages << new Output(outputMeta, renderedTemplate)
return
}
// Generate special pages
specialPages.each {
logger.info('processing specialPage: {}', it.path)
specialPageOutputTasks.each { specialPage, outputMeta ->
logger.info('processing specialPage: {}', specialPage.path)
def targetPath = stripExtension(it.path) + '.html'
def specialPageRenderResult = it.type.renderer.render(
it,
texts,
parts,
siteSpec,
globals,
targetPath
def specialPageRenderResult = specialPage.type.renderer.render(
specialPage,
new RenderContext(
config,
siteSpec,
globals,
texts,
parts,
outputMeta.sourcePath,
outputMeta.targetPath
)
)
String renderedSpecialPage
if (specialPageRenderResult.v1.size() > 0) {
@ -121,7 +143,8 @@ class SimpleStaticSiteGenerator implements StaticSiteGenerator {
}
// Create a GeneratedPage
generatedPages << new OutputPage(targetPath, renderedSpecialPage)
generatedPages << new Output(outputMeta, renderedSpecialPage)
return
}
logger.trace(exit, '\n\tdiagnostics: {}\n\tgeneratedPages: {}', diagnostics, generatedPages)

View File

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

View File

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

View File

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

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
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.tagbuilder.DynamicTagBuilder
import com.jessebrault.ssg.text.EmbeddableTextsCollection
import com.jessebrault.ssg.text.Text
import com.jessebrault.ssg.url.PathBasedUrlBuilder
import com.jessebrault.ssg.dsl.StandardDslMap
import com.jessebrault.ssg.renderer.RenderContext
import groovy.text.GStringTemplateEngine
import groovy.text.TemplateEngine
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import org.slf4j.LoggerFactory
@NullCheck
@EqualsAndHashCode
@ -23,35 +17,24 @@ class GspSpecialPageRenderer implements SpecialPageRenderer {
@Override
Tuple2<Collection<Diagnostic>, String> render(
SpecialPage specialPage,
Collection<Text> texts,
Collection<Part> parts,
SiteSpec siteSpec,
Map globals,
String targetPath
RenderContext context
) {
Collection<Diagnostic> diagnostics = []
def diagnostics = []
try {
def result = engine.createTemplate(specialPage.text).make([
globals: globals,
logger: LoggerFactory.getLogger("SpecialPage(${ specialPage.path })"),
parts: new EmbeddablePartsMap(
parts,
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)
])
def dslMap = StandardDslMap.get(context) {
it.loggerName = "GspSpecialPage(${ specialPage.path })"
it.onDiagnostics = diagnostics.&addAll
}
def result = engine.createTemplate(specialPage.text).make(dslMap)
new Tuple2<>(diagnostics, result.toString())
} 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
import com.jessebrault.ssg.input.InputPage
import com.jessebrault.ssg.task.Input
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@ -8,7 +8,7 @@ import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck
@EqualsAndHashCode
class SpecialPage implements InputPage {
class SpecialPage implements Input {
String text
String path

View File

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

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
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.tagbuilder.DynamicTagBuilder
import com.jessebrault.ssg.text.EmbeddableText
import com.jessebrault.ssg.text.FrontMatter
import com.jessebrault.ssg.dsl.StandardDslMap
import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.text.Text
import com.jessebrault.ssg.url.PathBasedUrlBuilder
import groovy.text.GStringTemplateEngine
import groovy.text.TemplateEngine
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import org.slf4j.LoggerFactory
@NullCheck
@EqualsAndHashCode
@ -24,32 +18,26 @@ class GspTemplateRenderer implements TemplateRenderer {
@Override
Tuple2<Collection<Diagnostic>, String> render(
Template template,
@Deprecated
FrontMatter frontMatter,
Text text,
Collection<Part> parts,
SiteSpec siteSpec,
Map globals,
String targetPath
RenderContext context
) {
def diagnostics = []
try {
def embeddableText = new EmbeddableText(text, globals, diagnostics.&addAll)
def result = engine.createTemplate(template.text).make([
frontMatter: frontMatter,
globals: globals,
logger: LoggerFactory.getLogger("Template(${ template.path })"),
parts: new EmbeddablePartsMap(parts, siteSpec, globals, diagnostics.&addAll, embeddableText, text.path, targetPath),
path: text.path,
siteSpec: siteSpec,
tagBuilder: new DynamicTagBuilder(),
targetPath: targetPath,
text: embeddableText,
urlBuilder: new PathBasedUrlBuilder(targetPath, siteSpec.baseUrl)
])
def dslMap = StandardDslMap.get(context) {
it.loggerName = "GspTemplate(${ template.path })"
it.onDiagnostics = diagnostics.&addAll
it.text = text
}
def result = engine.createTemplate(template.text).make(dslMap)
new Tuple2<>(diagnostics, result.toString())
} 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
import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.SiteSpec
import com.jessebrault.ssg.part.Part
import com.jessebrault.ssg.text.FrontMatter
import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.text.Text
interface TemplateRenderer {
/**
* TODO: get rid of frontMatter param since we can obtain it from the text
*/
Tuple2<Collection<Diagnostic>, String> render(
Template template,
@Deprecated
FrontMatter frontMatter,
Text text,
Collection<Part> parts,
SiteSpec siteSpec,
Map globals,
String targetPath
RenderContext context
)
}

View File

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

View File

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

View File

@ -71,7 +71,7 @@ class SimpleStaticSiteGeneratorIntegrationTests {
assertTrue(result.v2.size() == 1)
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)
}
@ -91,7 +91,7 @@ class SimpleStaticSiteGeneratorIntegrationTests {
assertTrue(result.v2.size() == 1)
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)
}
@ -107,11 +107,11 @@ class SimpleStaticSiteGeneratorIntegrationTests {
assertEmptyDiagnostics(result)
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)
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)
assertEquals('<p>Hello, World!</p>\n', specialPage.content)
}

View File

@ -1,224 +1,58 @@
package com.jessebrault.ssg.part
import com.jessebrault.ssg.SiteSpec
import com.jessebrault.ssg.text.EmbeddableText
import com.jessebrault.ssg.Diagnostic
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.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.assertDiagnosticException
import static com.jessebrault.ssg.testutil.DiagnosticsUtil.assertEmptyDiagnostics
import static com.jessebrault.ssg.testutil.DiagnosticsUtil.getDiagnosticsMessageSupplier
import static com.jessebrault.ssg.testutil.RenderContextUtil.getRenderContext
import static com.jessebrault.ssg.text.TextMocks.renderableText
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)
class GspPartRendererTests {
class GspPartRendererTests implements StandardDslConsumerTests {
private final PartRenderer renderer = new GspPartRenderer()
@Test
void rendersWithNoBindingOrGlobals() {
def part = new Part('', null, 'Hello, World!')
def r = this.renderer.render(
part,
[:],
new SiteSpec('', ''),
[:],
null,
[part],
'',
''
private Tuple2<Collection<Diagnostic>, String> doRender(
String scriptlet,
RenderContext context,
Map binding = [:],
Text text = null
) {
this.renderer.render(
new Part('', new PartType([], this.renderer), scriptlet),
binding,
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
void rendersWithBinding() {
def part = new Part('', null, "<%= binding['greeting'] %>")
def r = this.renderer.render(
part,
[greeting: 'Hello, World!'],
new SiteSpec('', ''),
[:],
null,
[part],
'',
''
this.checkResult(
'Hello, World!',
this.doRender('<%= binding.greeting %>', getRenderContext(), [greeting: 'Hello, World!'])
)
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
void textAvailable() {
def part = new Part('', null, '<%= text.render() %>')
def textDiagnostics = []
def r = this.renderer.render(
part,
this.checkResult('Hello, World!', this.renderer.render(
new Part('', new PartType([], this.renderer), '<%= text.render() %>'),
[:],
new SiteSpec('', ''),
[:],
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!')
}
getRenderContext(),
renderableText('Hello, World!')
))
}
@Test
@ -228,16 +62,16 @@ class GspPartRendererTests {
new PartType([], this.renderer),
'<% 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(
callerPart,
[:],
new SiteSpec('', ''),
[:],
null,
[callerPart, nestedProblemPart],
'',
''
getRenderContext(parts: [callerPart, nestedProblemPart]),
null
)
assertEquals(1, r.v1.size())
assertDiagnosticException(RuntimeException, r.v1[0])
@ -249,7 +83,7 @@ class GspPartRendererTests {
def nestedProblemPart = new Part(
'nestedProblem.gsp',
new PartType([], this.renderer),
'<% throw new RuntimeException() %>'
'<% throw new RuntimeException("nested problem exception") %>'
)
def callerPart = new Part(
'caller.gsp',
@ -259,15 +93,17 @@ class GspPartRendererTests {
def r = this.renderer.render(
callerPart,
[:],
new SiteSpec('', ''),
[:],
null,
[callerPart, nestedProblemPart],
'',
''
getRenderContext(parts: [callerPart, nestedProblemPart]),
null
)
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)
}

View File

@ -1,242 +1,22 @@
package com.jessebrault.ssg.specialpage
import com.jessebrault.ssg.SiteSpec
import com.jessebrault.ssg.part.Part
import com.jessebrault.ssg.part.PartRenderer
import com.jessebrault.ssg.part.PartType
import com.jessebrault.ssg.text.*
import org.junit.jupiter.api.Test
import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.dsl.StandardDslConsumerTests
import com.jessebrault.ssg.renderer.RenderContext
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 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)
class GspSpecialPageRendererTests {
class GspSpecialPageRendererTests implements StandardDslConsumerTests {
private final SpecialPageRenderer renderer = new GspSpecialPageRenderer()
@Test
void rendersGlobal() {
def specialPage = new SpecialPage("<%= globals['greeting'] %>", 'test.gsp', null)
def globals = [greeting: 'Hello, World!']
def r = this.renderer.render(
specialPage,
[],
[],
new SiteSpec('', ''),
globals,
''
@Override
Tuple2<Collection<Diagnostic>, String> render(String scriptlet, RenderContext context) {
this.renderer.render(
new SpecialPage(scriptlet, '', new SpecialPageType([], this.renderer)),
context
)
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
import com.jessebrault.ssg.SiteSpec
import com.jessebrault.ssg.part.Part
import com.jessebrault.ssg.part.PartRenderer
import com.jessebrault.ssg.part.PartType
import com.jessebrault.ssg.text.FrontMatter
import com.jessebrault.ssg.Diagnostic
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.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.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.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)
class GspTemplateRendererTests {
class GspTemplateRendererTests implements StandardDslConsumerTests {
private final TemplateRenderer renderer = new GspTemplateRenderer()
@Test
void rendersPartWithNoBinding(@Mock PartRenderer partRenderer) {
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)
private Tuple2<Collection<Diagnostic>, String> doRender(String scriptlet, Text text, RenderContext context) {
this.renderer.render(new Template(scriptlet, '', null), text, context)
}
@Test
void rendersPartWithBinding(@Mock PartRenderer partRenderer) {
def template = new Template(
"<%= 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)
@Override
Tuple2<Collection<Diagnostic>, String> render(String scriptlet, RenderContext context) {
this.doRender(scriptlet, blankText(), context)
}
/**
* TODO: refactor this and the super interface methods so that we can re-use rendering logic
*/
@Test
void textAvailableToRender() {
def template = new Template('<%= text.render() %>', null, null)
def r = this.renderer.render(
template,
new FrontMatter(null, [:]),
renderableText('Hello, World!'),
[],
new SiteSpec('', ''),
[:],
''
getRenderContext()
)
assertTrue(r.v1.isEmpty(), getDiagnosticsMessageSupplier(r.v1))
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.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.assertTrue
@ -23,10 +25,18 @@ class DiagnosticsUtil {
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, {
"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))
}
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))
}
}