Major refactoring and introduction of Task model.

This commit is contained in:
JesseBrault0709 2023-02-21 15:26:37 +01:00
parent 107f394b82
commit 92c810870c
40 changed files with 742 additions and 240 deletions

View File

@ -8,6 +8,7 @@ import com.jessebrault.ssg.part.PartType
import com.jessebrault.ssg.specialpage.GspSpecialPageRenderer
import com.jessebrault.ssg.specialpage.SpecialPageFileSpecialPagesProvider
import com.jessebrault.ssg.specialpage.SpecialPageType
import com.jessebrault.ssg.task.TaskExecutorContext
import com.jessebrault.ssg.template.GspTemplateRenderer
import com.jessebrault.ssg.template.TemplateFileTemplatesProvider
import com.jessebrault.ssg.template.TemplateType
@ -80,16 +81,28 @@ abstract class AbstractBuildCommand extends AbstractSubCommand {
// Do each build
this.builds.each {
def result = this.ssg.generate(it)
if (result.v1.size() > 0) {
if (result.hasDiagnostics()) {
hadDiagnostics = true
result.v1.each {
result.diagnostics.each {
logger.error(it.message)
}
} else {
result.v2.each { Output output ->
def target = new File(it.outDir, output.meta.targetPath)
target.createParentDirectories()
target.write(output.content)
def tasks = result.get()
Collection<Diagnostic> executionDiagnostics = []
def context = new TaskExecutorContext(
it,
tasks,
null,
{ Collection<Diagnostic> diagnostics ->
executionDiagnostics.addAll(diagnostics)
}
)
result.get().each { it.execute(context) }
if (!executionDiagnostics.isEmpty()) {
hadDiagnostics = true
executionDiagnostics.each {
logger.error(it.message)
}
}
}
}

View File

@ -0,0 +1,28 @@
package com.jessebrault.ssg
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false, includeFields = true)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
final class Result<T> {
final Collection<Diagnostic> diagnostics
private final T t
boolean hasDiagnostics() {
!this.diagnostics.isEmpty()
}
T get() {
this.t
}
@Override
String toString() {
"Result(diagnostics: ${ this.diagnostics }, t: ${ this.t })"
}
}

View File

@ -1,25 +1,15 @@
package com.jessebrault.ssg
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 com.jessebrault.ssg.task.SpecialPageToHtmlFileTaskFactory
import com.jessebrault.ssg.task.TaskContainer
import com.jessebrault.ssg.task.TextToHtmlFileTaskFactory
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.slf4j.Marker
import org.slf4j.MarkerFactory
import static com.jessebrault.ssg.util.ExtensionsUtil.stripExtension
@TupleConstructor(includeFields = true)
@NullCheck
@EqualsAndHashCode(includeFields = true)
class SimpleStaticSiteGenerator implements StaticSiteGenerator {
private static final Logger logger = LoggerFactory.getLogger(SimpleStaticSiteGenerator)
@ -27,128 +17,25 @@ class SimpleStaticSiteGenerator implements StaticSiteGenerator {
private static final Marker exit = MarkerFactory.getMarker('EXIT')
@Override
Tuple2<Collection<Diagnostic>, Collection<Output>> generate(Build build) {
Result<TaskContainer> generate(Build build) {
logger.trace(enter, 'build: {}', build)
logger.info('processing build with name: {}', build.name)
def config = build.config
def siteSpec = build.siteSpec
def tasks = new TaskContainer()
def diagnostics = []
// Get all texts, templates, parts, and specialPages
def texts = config.textProviders.collectMany { it.provide() }
def templates = config.templatesProviders.collectMany { it.provide() }
def parts = config.partsProviders.collectMany { it.provide() }
def specialPages = config.specialPagesProviders.collectMany { it.provide() }
def textToHtmlFactory = new TextToHtmlFileTaskFactory()
def textsResult = textToHtmlFactory.getTasks(build)
tasks.addAll(textsResult.get())
diagnostics.addAll(textsResult.diagnostics)
logger.debug('\n\ttexts: {}\n\ttemplates: {}\n\tparts: {}\n\tspecialPages: {}', texts, templates, parts, specialPages)
def specialPageToHtmlFactory = new SpecialPageToHtmlFileTaskFactory()
def specialPagesResult = specialPageToHtmlFactory.getTasks(build)
tasks.addAll(specialPagesResult.get())
diagnostics.addAll(specialPagesResult.diagnostics)
def globals = build.globals
Collection<Diagnostic> diagnostics = []
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
textOutputTasks.each { text, outputMeta ->
logger.trace(enter, 'text: {}, outputMeta: {}', text, outputMeta)
logger.info('processing text: {}', text.path)
// Extract frontMatter from text
def frontMatterResult = text.type.frontMatterGetter.get(text)
FrontMatter frontMatter
if (frontMatterResult.v1.size() > 0) {
logger.debug('diagnostics for getting frontMatter for {}: {}', text.path, frontMatterResult.v1)
diagnostics.addAll(frontMatterResult.v1)
logger.trace(exit, '')
return
} else {
frontMatter = frontMatterResult.v2
logger.debug('frontMatter: {}', frontMatter)
}
// 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', text)
return
}
def template = templates.find { it.path == desiredTemplate.get() }
if (template == 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)
// Render the template using the result of rendering the text earlier
def templateRenderResult = template.type.renderer.render(
template,
text,
new RenderContext(
config,
siteSpec,
globals,
texts,
parts,
outputMeta.sourcePath,
outputMeta.targetPath
)
)
String renderedTemplate
if (templateRenderResult.v1.size() > 0) {
diagnostics.addAll(templateRenderResult.v1)
logger.trace(exit, '')
return
} else {
renderedTemplate = templateRenderResult.v2
}
// Create a GeneratedPage
generatedPages << new Output(outputMeta, renderedTemplate)
return
}
// Generate special pages
specialPageOutputTasks.each { specialPage, outputMeta ->
logger.info('processing specialPage: {}', specialPage.path)
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) {
diagnostics.addAll(specialPageRenderResult.v1)
logger.trace(exit, '')
return
} else {
renderedSpecialPage = specialPageRenderResult.v2
}
// Create a GeneratedPage
generatedPages << new Output(outputMeta, renderedSpecialPage)
return
}
logger.trace(exit, '\n\tdiagnostics: {}\n\tgeneratedPages: {}', diagnostics, generatedPages)
new Tuple2<>(diagnostics, generatedPages)
logger.trace(exit, '\n\tdiagnostics: {}\n\ttasks: {}', diagnostics, tasks)
new Result<>(diagnostics, tasks)
}
@Override

View File

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

View File

@ -58,6 +58,7 @@ final class StandardDslMap {
it.siteSpec = context.siteSpec
it.sourcePath = context.sourcePath
it.targetPath = context.targetPath
it.tasks = context.tasks
it.text = b.text ? new EmbeddableText(
b.text,
context.globals,

View File

@ -3,6 +3,8 @@ 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.task.Task
import com.jessebrault.ssg.task.TaskContainer
import com.jessebrault.ssg.text.Text
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
@ -19,4 +21,6 @@ final class RenderContext {
final Collection<Part> parts
final String sourcePath
final String targetPath
final TaskContainer tasks
final Set<Class<? extends Task>> taskTypes
}

View File

@ -1,18 +1,17 @@
package com.jessebrault.ssg.specialpage
import com.jessebrault.ssg.task.Input
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
class SpecialPage implements Input {
final class SpecialPage {
String text
String path
SpecialPageType type
final String text
final String path
final SpecialPageType type
@Override
String toString() {

View File

@ -0,0 +1,31 @@
package com.jessebrault.ssg.task
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
@NullCheck
@EqualsAndHashCode
abstract class AbstractTask<T extends Task> implements Task {
final TaskType<T> type
final String name
AbstractTask(TaskType<T> type, String name) {
this.type = type
this.name = name
}
protected abstract T getThis()
@Override
void execute(TaskExecutorContext context) {
// I am guessing that if we put this.getThis(), it will think the runtime type is AbstractTask? Not sure.
this.type.executor.execute(getThis(), context)
}
@Override
String toString() {
"AbstractTask(name: ${ this.name }, type: ${ this.type })"
}
}

View File

@ -0,0 +1,5 @@
package com.jessebrault.ssg.task
interface FileInput extends Input {
File getFile()
}

View File

@ -0,0 +1,6 @@
package com.jessebrault.ssg.task
interface FileOutput extends Output {
File getFile()
String getContent()
}

View File

@ -0,0 +1,26 @@
package com.jessebrault.ssg.task
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false, includeFields = true)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
final class HtmlFileOutput {
final File file
final String htmlPath
private final Closure<String> contentClosure
String getContent(TaskContainer tasks, TaskTypeContainer taskTypes, Closure onDiagnostics) {
this.contentClosure(tasks, taskTypes, onDiagnostics)
}
@Override
String toString() {
"HtmlFileOutput(file: ${ this.file }, htmlPath: ${ this.htmlPath })"
}
}

View File

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

View File

@ -1,20 +1,5 @@
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 })"
}
interface Output {
String getName()
}

View File

@ -1,19 +0,0 @@
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,50 @@
package com.jessebrault.ssg.task
import com.jessebrault.ssg.specialpage.SpecialPage
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
@NullCheck
@EqualsAndHashCode
final class SpecialPageToHtmlFileTask extends AbstractTask<SpecialPageToHtmlFileTask> {
private static final class SpecialPageToHtmlFileTaskExecutor implements TaskExecutor<SpecialPageToHtmlFileTask> {
@Override
void execute(SpecialPageToHtmlFileTask task, TaskExecutorContext context) {
task.output.file.write(task.output.getContent(
context.allTasks, context.allTypes, context.onDiagnostics
))
}
@Override
String toString() {
'SpecialPageToHtmlFileTaskExecutor()'
}
}
private static final TaskType<SpecialPageToHtmlFileTask> type = new TaskType<>(
'specialPageToHtmlFile', new SpecialPageToHtmlFileTaskExecutor()
)
final SpecialPage input
final HtmlFileOutput output
SpecialPageToHtmlFileTask(String name, SpecialPage input, HtmlFileOutput output) {
super(type, name)
this.input = input
this.output = output
}
@Override
protected SpecialPageToHtmlFileTask getThis() {
this
}
@Override
String toString() {
"SpecialPageToHtmlFileTask(input: ${ this.input }, output: ${ this.output }, super: ${ super })"
}
}

View File

@ -0,0 +1,82 @@
package com.jessebrault.ssg.task
import com.jessebrault.ssg.Build
import com.jessebrault.ssg.Result
import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.util.ExtensionsUtil
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.slf4j.Marker
import org.slf4j.MarkerFactory
final class SpecialPageToHtmlFileTaskFactory implements TaskFactory<SpecialPageToHtmlFileTask> {
private static final Logger logger = LoggerFactory.getLogger(SpecialPageToHtmlFileTaskFactory)
private static final Marker enter = MarkerFactory.getMarker('ENTER')
private static final Marker exit = MarkerFactory.getMarker('EXIT')
@Override
Result<TaskCollection<SpecialPageToHtmlFileTask>> getTasks(Build build) {
logger.trace(enter, 'build: {}', build)
logger.info('processing build with name {} for SpecialPageToHtmlFileTasks', build.name)
def config = build.config
def siteSpec = build.siteSpec
def globals = build.globals
def specialPages = config.specialPagesProviders.collectMany { it.provide() }
def templates = config.templatesProviders.collectMany { it.provide() }
def parts = config.partsProviders.collectMany { it.provide() }
def texts = config.textProviders.collectMany { it.provide() }
logger.debug('\n\tspecialPages: {}\n\ttemplates: {}\n\tparts: {}', specialPages, templates, parts)
def tasks = new TaskCollection<SpecialPageToHtmlFileTask>(specialPages.findResults {
logger.trace(enter, 'specialPage: {}', it)
logger.info('processing specialPage with path: {}', it.path)
def htmlPath = ExtensionsUtil.stripExtension(it.path) + '.html'
def renderSpecialPage = { TaskContainer tasks, TaskTypeContainer taskTypes, Closure<Void> onDiagnostics ->
def renderResult = it.type.renderer.render(
it,
new RenderContext(
config,
siteSpec,
globals,
texts,
parts,
it.path,
htmlPath,
tasks,
taskTypes
)
)
if (!renderResult.v1.isEmpty()) {
onDiagnostics(renderResult.v1)
''
} else {
renderResult.v2
}
}
def result = new SpecialPageToHtmlFileTask(
"specialPageToHtmlFileTask:${ it.path }:${ htmlPath }",
it,
new HtmlFileOutput(
new File(build.outDir, htmlPath),
htmlPath,
renderSpecialPage
)
)
logger.trace(exit, 'result: {}', result)
result
})
def result = new Result<>([], tasks)
logger.trace(exit, 'result: {}', result)
result
}
}

View File

@ -1,6 +1,6 @@
package com.jessebrault.ssg.task
interface Task {
Output getOutput()
OutputMeta getOutputMeta()
String getName()
void execute(TaskExecutorContext context)
}

View File

@ -0,0 +1,24 @@
package com.jessebrault.ssg.task
import static java.util.Objects.requireNonNull
class TaskCollection<T extends Task> {
@Delegate
private final Collection<T> tasks = new ArrayList<T>()
TaskCollection(Collection<? extends T> tasks = null) {
if (tasks != null) {
this.tasks.addAll(requireNonNull(tasks))
}
}
def <U extends T> TaskCollection<U> findAllByType(
Class<U> taskClass
) {
new TaskCollection<>(this.tasks.findResults {
taskClass.isAssignableFrom(it.class) ? taskClass.cast(it) : null
})
}
}

View File

@ -0,0 +1,9 @@
package com.jessebrault.ssg.task
final class TaskContainer extends TaskCollection<Task> {
TaskContainer(Collection<? extends Task> tasks = null) {
super(tasks)
}
}

View File

@ -1,21 +0,0 @@
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,5 @@
package com.jessebrault.ssg.task
interface TaskExecutor<T extends Task> {
void execute(T task, TaskExecutorContext context)
}

View File

@ -0,0 +1,23 @@
package com.jessebrault.ssg.task
import com.jessebrault.ssg.Build
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
final class TaskExecutorContext {
final Build build
final TaskContainer allTasks
final TaskTypeContainer allTypes
final Closure onDiagnostics
@Override
String toString() {
"TaskExecutorContext(build: ${ this.build }, allTasks: ${ this.allTasks }, allTypes: ${ this.allTypes })"
}
}

View File

@ -1,7 +1,8 @@
package com.jessebrault.ssg.task
import org.jetbrains.annotations.Nullable
import com.jessebrault.ssg.Build
import com.jessebrault.ssg.Result
interface TaskFactory {
@Nullable Task getTask(Input input, TaskContext context)
interface TaskFactory<T extends Task> {
Result<TaskCollection<T>> getTasks(Build build)
}

View File

@ -5,16 +5,16 @@ import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
final class OutputMeta {
final class TaskType<T extends Task> {
final String sourcePath
final String targetPath
final String name
final TaskExecutor<T> executor
@Override
String toString() {
"OutputMeta(sourcePath: ${ sourcePath }, targetPath: ${ targetPath })"
"TaskType(${ this.name }, ${ this.executor })"
}
}

View File

@ -0,0 +1,36 @@
package com.jessebrault.ssg.task
import java.util.regex.Pattern
final class TaskTypeContainer {
private static final Pattern taskTypeNamePattern = ~/(.*)Task/
@Delegate
private final Set<Class<? extends Task>> taskTypes = []
TaskTypeContainer(Collection<Class<? extends Task>> taskTypes = null) {
if (taskTypes) {
this.taskTypes.addAll(taskTypes)
}
}
@Override
Class<? extends Task> getProperty(String propertyName) {
def result = this.taskTypes.find {
def m = taskTypeNamePattern.matcher(it.simpleName)
if (m.matches()) {
def withoutTaskEnd = m.group(1)
def uncapitalized = withoutTaskEnd.uncapitalize()
return propertyName == uncapitalized
} else {
throw new IllegalStateException("invalid task type name: ${ it.simpleName }")
}
}
if (!result) {
throw new IllegalStateException("no such taskType: ${ propertyName }")
}
result
}
}

View File

@ -0,0 +1,14 @@
package com.jessebrault.ssg.task
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 TextInput implements Input {
final String name
final Text text
}

View File

@ -0,0 +1,50 @@
package com.jessebrault.ssg.task
import com.jessebrault.ssg.text.Text
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
@NullCheck
@EqualsAndHashCode(callSuper = true)
final class TextToHtmlFileTask extends AbstractTask<TextToHtmlFileTask> {
private static final class TextToHtmlFileTaskExecutor implements TaskExecutor<TextToHtmlFileTask> {
@Override
void execute(TextToHtmlFileTask task, TaskExecutorContext context) {
task.output.file.write(task.output.getContent(
context.allTasks, context.allTypes, context.onDiagnostics
))
}
@Override
String toString() {
'TextToHtmlFileTaskExecutor()'
}
}
private static final TaskType<TextToHtmlFileTask> type = new TaskType<>(
'textToHtmlFile', new TextToHtmlFileTaskExecutor()
)
final Text input
final HtmlFileOutput output
TextToHtmlFileTask(String name, Text input, HtmlFileOutput output) {
super(type, name)
this.input = input
this.output = output
}
@Override
protected TextToHtmlFileTask getThis() {
this
}
@Override
String toString() {
"TextToHtmlFileTask(input: ${ this.input }, output: ${ this.output }, super: ${ super })"
}
}

View File

@ -0,0 +1,112 @@
package com.jessebrault.ssg.task
import com.jessebrault.ssg.Build
import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.Result
import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.text.FrontMatter
import com.jessebrault.ssg.util.ExtensionsUtil
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.slf4j.Marker
import org.slf4j.MarkerFactory
final class TextToHtmlFileTaskFactory implements TaskFactory<TextToHtmlFileTask> {
private static final Logger logger = LoggerFactory.getLogger(TextToHtmlFileTaskFactory)
private static final Marker enter = MarkerFactory.getMarker('ENTER')
private static final Marker exit = MarkerFactory.getMarker('EXIT')
@Override
Result<TaskCollection<TextToHtmlFileTask>> getTasks(Build build) {
logger.trace(enter, 'build: {}', build)
logger.info('getting TextToHtmlFileTasks for build with name: {}', build.name)
def config = build.config
def siteSpec = build.siteSpec
def globals = build.globals
def diagnostics = []
// Get all texts, templates, parts, and specialPages
def texts = config.textProviders.collectMany { it.provide() }
def templates = config.templatesProviders.collectMany { it.provide() }
def parts = config.partsProviders.collectMany { it.provide() }
logger.debug('\n\ttexts: {}\n\ttemplates: {}\n\tparts: {}', texts, templates, parts)
def tasks = new TaskCollection<TextToHtmlFileTask>(texts.findResults {
logger.trace(enter, 'text: {}', it)
logger.info('processing text with path: {}', it.path)
def frontMatterResult = it.type.frontMatterGetter.get(it)
FrontMatter frontMatter
if (!frontMatterResult.v1.isEmpty()) {
diagnostics.addAll(frontMatterResult.v1)
logger.trace(exit, 'result: {}', null)
return null
} else {
frontMatter = frontMatterResult.v2
logger.debug('frontMatter: {}', frontMatter)
}
def desiredTemplate = frontMatter.find('template')
if (desiredTemplate.isEmpty()) {
logger.info('text with path {} has no \'template\' key in its frontMatter; skipping', it.path)
logger.trace(exit, 'result: {}', null)
return null
}
def template = templates.find { it.path == desiredTemplate.get() }
if (template == null) {
diagnostics << new Diagnostic("in text with path ${ it.path }, frontMatter.template refers to an unknown template: ${ desiredTemplate.get() }")
logger.trace(exit, 'result: {}', null)
return null
}
logger.debug('found template: {}', template)
def htmlPath = ExtensionsUtil.stripExtension(it.path) + '.html'
def renderTemplate = { TaskContainer tasks, TaskTypeContainer taskTypes, Closure<Void> onDiagnostics ->
def templateRenderResult = template.type.renderer.render(
template,
it,
new RenderContext(
config,
siteSpec,
globals,
texts,
parts,
it.path,
htmlPath,
tasks,
taskTypes
)
)
if (!templateRenderResult.v1.isEmpty()) {
onDiagnostics(templateRenderResult.v1)
''
} else {
templateRenderResult.v2
}
}
def result = new TextToHtmlFileTask(
"textToHtmlFileTask:${ it.path }:${ htmlPath }",
it,
new HtmlFileOutput(
new File(build.outDir, htmlPath),
htmlPath,
renderTemplate
)
)
logger.trace(exit, 'result: {}', result)
result
})
def result = new Result<>(diagnostics, tasks)
logger.trace(exit, 'result: {}', result)
result
}
}

View File

@ -0,0 +1,5 @@
package com.jessebrault.ssg.task
interface WithInput<I extends Input> {
I getInput()
}

View File

@ -0,0 +1,5 @@
package com.jessebrault.ssg.task
interface WithOutput<O extends Output> {
O getOutput()
}

View File

@ -1,18 +1,17 @@
package com.jessebrault.ssg.text
import com.jessebrault.ssg.task.Input
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
class Text implements Input {
final class Text {
String text
String path
TextType type
final String text
final String path
final TextType type
@Override
String toString() {

View File

@ -6,6 +6,10 @@ import com.jessebrault.ssg.part.PartType
import com.jessebrault.ssg.specialpage.GspSpecialPageRenderer
import com.jessebrault.ssg.specialpage.SpecialPageFileSpecialPagesProvider
import com.jessebrault.ssg.specialpage.SpecialPageType
import com.jessebrault.ssg.task.SpecialPageToHtmlFileTask
import com.jessebrault.ssg.task.Task
import com.jessebrault.ssg.task.TaskTypeContainer
import com.jessebrault.ssg.task.TextToHtmlFileTask
import com.jessebrault.ssg.template.GspTemplateRenderer
import com.jessebrault.ssg.template.TemplateFileTemplatesProvider
import com.jessebrault.ssg.template.TemplateType
@ -18,6 +22,7 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import static com.jessebrault.ssg.testutil.DiagnosticsUtil.assertEmptyDiagnostics
import static com.jessebrault.ssg.testutil.DiagnosticsUtil.getDiagnosticsMessageSupplier
import static org.junit.jupiter.api.Assertions.*
class SimpleStaticSiteGeneratorIntegrationTests {
@ -68,11 +73,15 @@ class SimpleStaticSiteGeneratorIntegrationTests {
def result = this.ssg.generate(this.build)
assertEmptyDiagnostics(result)
assertTrue(result.v2.size() == 1)
def tasks = result.get()
assertTrue(tasks.size() == 1)
def p0 = result.v2[0]
assertEquals('test.html', p0.meta.targetPath)
assertEquals('<p><strong>Hello, World!</strong></p>\n', p0.content)
def t0 = tasks.findAllByType(TextToHtmlFileTask)[0]
assertEquals('test.html', t0.output.htmlPath)
def contentResult = t0.output.getContent(tasks, new TaskTypeContainer([TextToHtmlFileTask])) { Collection<Diagnostic> diagnostics ->
fail(getDiagnosticsMessageSupplier(diagnostics))
}
assertEquals('<p><strong>Hello, World!</strong></p>\n', contentResult)
}
@Test
@ -88,11 +97,18 @@ class SimpleStaticSiteGeneratorIntegrationTests {
def result = this.ssg.generate(this.build)
assertEmptyDiagnostics(result)
assertTrue(result.v2.size() == 1)
def tasks = result.get()
assertTrue(tasks.size() == 1)
def p0 = result.v2[0]
assertEquals('nested/nested.html', p0.meta.targetPath)
assertEquals('<p><strong>Hello, World!</strong></p>\n', p0.content)
def t0 = tasks.findAllByType(TextToHtmlFileTask)[0]
assertEquals('nested/nested.html', t0.output.htmlPath)
def contentResult = t0.output.getContent(
tasks,
new TaskTypeContainer([TextToHtmlFileTask])
) { Collection<Diagnostic> diagnostics ->
fail(getDiagnosticsMessageSupplier(diagnostics))
}
assertEquals('<p><strong>Hello, World!</strong></p>\n', contentResult)
}
@Test
@ -105,15 +121,28 @@ class SimpleStaticSiteGeneratorIntegrationTests {
def result = this.ssg.generate(this.build)
assertEmptyDiagnostics(result)
assertEquals(2, result.v2.size())
def tasks = result.get()
assertEquals(2, tasks.size())
def testPage = result.v2.find { it.meta.targetPath == 'test.html' }
assertNotNull(testPage)
assertEquals('2', testPage.content)
def taskTypes = new TaskTypeContainer([TextToHtmlFileTask, SpecialPageToHtmlFileTask])
def specialPage = result.v2.find { it.meta.targetPath == 'special.html' }
assertNotNull(specialPage)
assertEquals('<p>Hello, World!</p>\n', specialPage.content)
def testPageTask = tasks.findAllByType(TextToHtmlFileTask).find {
it.output.htmlPath == 'test.html'
}
assertNotNull(testPageTask)
def testPageContent = testPageTask.output.getContent(tasks, taskTypes) { Collection<Diagnostic> diagnostics ->
fail(getDiagnosticsMessageSupplier(diagnostics))
}
assertEquals('2', testPageContent)
def specialPageTask = tasks.findAllByType(SpecialPageToHtmlFileTask).find {
it.output.htmlPath == 'special.html'
}
assertNotNull(specialPageTask)
def specialPageContent = specialPageTask.output.getContent(tasks, taskTypes) { Collection<Diagnostic> diagnostics ->
fail(getDiagnosticsMessageSupplier(diagnostics))
}
assertEquals('<p>Hello, World!</p>\n', specialPageContent)
}
@Test
@ -121,7 +150,7 @@ class SimpleStaticSiteGeneratorIntegrationTests {
new File(this.textsDir, 'test.md').write('Hello, World!')
def result = this.ssg.generate(this.build)
assertEmptyDiagnostics(result)
assertEquals(0, result.v2.size())
assertEquals(0, result.get().size())
}
}

View File

@ -2,6 +2,7 @@ package com.jessebrault.ssg.text
import org.junit.jupiter.api.Test
import static com.jessebrault.ssg.text.TextMocks.renderableText
import static org.junit.jupiter.api.Assertions.assertEquals
class ExcerptGetterTests {
@ -10,7 +11,7 @@ class ExcerptGetterTests {
@Test
void takesAllIfTextLessThanLimit() {
def text = new Text('One Two Three Four Five', null, null)
def text = renderableText('One Two Three Four Five')
def result = this.excerptGetter.getExcerpt(text, 10)
assertEquals(0, result.v1.size())
assertEquals('One Two Three Four Five', result.v2)
@ -18,7 +19,7 @@ class ExcerptGetterTests {
@Test
void takesTheLimit() {
def text = new Text('One Two Three Four Five', null, null)
def text = renderableText('One Two Three Four Five')
def result = this.excerptGetter.getExcerpt(text, 2)
assertEquals(0, result.v1.size())
assertEquals('One Two', result.v2)
@ -26,7 +27,7 @@ class ExcerptGetterTests {
@Test
void worksWithHeading() {
def text = new Text('# Heading\nOne Two Three', null, null)
def text = renderableText('# Heading\nOne Two Three')
def result = this.excerptGetter.getExcerpt(text, 1)
assertEquals(0, result.v1.size())
assertEquals('Heading', result.v2)
@ -34,7 +35,7 @@ class ExcerptGetterTests {
@Test
void worksWithFrontMatter() {
def text = new Text('---\ntest: hello\n---\nOne Two Three', null, null)
def text = renderableText('---\ntest: hello\n---\nOne Two Three')
def result = this.excerptGetter.getExcerpt(text, 1)
assertEquals(0, result.v1.size())
assertEquals('One', result.v2)

View File

@ -8,6 +8,10 @@ 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.task.HtmlFileOutput
import com.jessebrault.ssg.task.TaskContainer
import com.jessebrault.ssg.task.TaskTypeContainer
import com.jessebrault.ssg.task.TextToHtmlFileTask
import com.jessebrault.ssg.text.EmbeddableTextsCollection
import com.jessebrault.ssg.url.UrlBuilder
import org.junit.jupiter.api.Test
@ -18,8 +22,11 @@ import org.mockito.junit.jupiter.MockitoExtension
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import static com.jessebrault.ssg.task.SpecialPageToHtmlFileTaskMocks.blankSpecialPageToHtmlFileTask
import static com.jessebrault.ssg.task.TextToHtmlFileTaskMocks.blankTextToHtmlFileTask
import static com.jessebrault.ssg.testutil.DiagnosticsUtil.assertEmptyDiagnostics
import static com.jessebrault.ssg.testutil.RenderContextUtil.getRenderContext
import static com.jessebrault.ssg.text.TextMocks.blankText
import static com.jessebrault.ssg.text.TextMocks.renderableTextWithPath
import static org.junit.jupiter.api.Assertions.assertEquals
import static org.junit.jupiter.api.Assertions.fail
@ -146,6 +153,48 @@ interface StandardDslConsumerTests {
)
}
@Test
default void tasksAvailable() {
this.doDslAssertionTest("<% assert tasks != null && tasks instanceof ${ TaskContainer.name } %>")
}
@Test
default void tasksFind() {
def task = new TextToHtmlFileTask(
'testTask',
blankText(),
new HtmlFileOutput(
new File('test.html'),
'test.html',
{ '' }
)
)
this.doDslRenderTest(
'test.html',
'<%= tasks.find { it.name == "testTask" }.output.htmlPath %>',
getRenderContext(tasks: new TaskContainer([task]))
)
}
@Test
default void taskTypesAvailable() {
this.doDslAssertionTest(
"<% assert taskTypes != null && taskTypes instanceof ${ TaskTypeContainer.name } %>"
)
}
@Test
default void taskFindAllByType() {
def t0 = blankTextToHtmlFileTask()
def t1 = blankSpecialPageToHtmlFileTask()
this.doDslAssertionTest(
'<% assert tasks.size() == 2 && ' +
'tasks.findAllByType(taskTypes.textToHtmlFile).size() == 1 &&' +
'tasks.findAllByType(taskTypes.specialPageToHtmlFile).size() == 1 %>',
getRenderContext(tasks: new TaskContainer([t0, t1]))
)
}
@Test
default void textsAvailable() {
this.doDslAssertionTest(

View File

@ -0,0 +1,16 @@
package com.jessebrault.ssg.specialpage
import static org.mockito.Mockito.mock
final class SpecialPageMocks {
static SpecialPage blankSpecialPage() {
def renderer = mock(SpecialPageRenderer)
new SpecialPage(
'',
'',
new SpecialPageType([], renderer)
)
}
}

View File

@ -0,0 +1,19 @@
package com.jessebrault.ssg.task
import static com.jessebrault.ssg.specialpage.SpecialPageMocks.blankSpecialPage
final class SpecialPageToHtmlFileTaskMocks {
static SpecialPageToHtmlFileTask blankSpecialPageToHtmlFileTask() {
new SpecialPageToHtmlFileTask(
'',
blankSpecialPage(),
new HtmlFileOutput(
new File(''),
'',
{ '' }
)
)
}
}

View File

@ -0,0 +1,19 @@
package com.jessebrault.ssg.task
import static com.jessebrault.ssg.text.TextMocks.blankText
final class TextToHtmlFileTaskMocks {
static TextToHtmlFileTask blankTextToHtmlFileTask() {
new TextToHtmlFileTask(
'',
blankText(),
new HtmlFileOutput(
new File(''),
'',
{ '' }
)
)
}
}

View File

@ -1,6 +1,7 @@
package com.jessebrault.ssg.testutil
import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.Result
import com.jessebrault.ssg.text.ExcerptGetter
import groovy.transform.stc.ClosureParams
import groovy.transform.stc.FirstParam
@ -25,6 +26,10 @@ class DiagnosticsUtil {
assertTrue(result.v1.isEmpty(), getDiagnosticsMessageSupplier(result.v1))
}
static void assertEmptyDiagnostics(Result<?> result) {
assertTrue(!result.hasDiagnostics(), getDiagnosticsMessageSupplier(result.diagnostics))
}
static <E extends Exception> void assertDiagnosticException(
Class<E> expectedException,
Diagnostic diagnostic,

View File

@ -3,6 +3,8 @@ package com.jessebrault.ssg.testutil
import com.jessebrault.ssg.Config
import com.jessebrault.ssg.SiteSpec
import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.task.Task
import com.jessebrault.ssg.task.TaskContainer
class RenderContextUtil {
@ -14,7 +16,9 @@ class RenderContextUtil {
args?.texts as Collection ?: [],
args?.parts as Collection ?: [],
args?.sourcePath as String ?: '',
args?.targetPath as String ?: ''
args?.targetPath as String ?: '',
args?.tasks as TaskContainer ?: new TaskContainer(),
args?.taskTypes as Set<Class<? extends Task>> ?: [] as Set<Class<? extends Task>>
)
}

View File

@ -18,7 +18,7 @@ class TextMocks {
when(textRenderer.render(any(), any())).thenReturn(new Tuple2<>([], text))
def frontMatterGetter = mock(FrontMatterGetter)
def excerptGetter = mock(ExcerptGetter)
new Text('', '', new TextType([], textRenderer, frontMatterGetter, excerptGetter))
new Text(text, '', new TextType([], textRenderer, frontMatterGetter, excerptGetter))
}
static Text textWithPath(String path) {
@ -33,7 +33,7 @@ class TextMocks {
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))
new Text(text, path, new TextType([], textRenderer, frontMatterGetter, excerptGetter))
}
}