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.GspSpecialPageRenderer
import com.jessebrault.ssg.specialpage.SpecialPageFileSpecialPagesProvider import com.jessebrault.ssg.specialpage.SpecialPageFileSpecialPagesProvider
import com.jessebrault.ssg.specialpage.SpecialPageType import com.jessebrault.ssg.specialpage.SpecialPageType
import com.jessebrault.ssg.task.TaskExecutorContext
import com.jessebrault.ssg.template.GspTemplateRenderer import com.jessebrault.ssg.template.GspTemplateRenderer
import com.jessebrault.ssg.template.TemplateFileTemplatesProvider import com.jessebrault.ssg.template.TemplateFileTemplatesProvider
import com.jessebrault.ssg.template.TemplateType import com.jessebrault.ssg.template.TemplateType
@ -80,16 +81,28 @@ abstract class AbstractBuildCommand extends AbstractSubCommand {
// Do each build // Do each build
this.builds.each { this.builds.each {
def result = this.ssg.generate(it) def result = this.ssg.generate(it)
if (result.v1.size() > 0) { if (result.hasDiagnostics()) {
hadDiagnostics = true hadDiagnostics = true
result.v1.each { result.diagnostics.each {
logger.error(it.message) logger.error(it.message)
} }
} else { } else {
result.v2.each { Output output -> def tasks = result.get()
def target = new File(it.outDir, output.meta.targetPath) Collection<Diagnostic> executionDiagnostics = []
target.createParentDirectories() def context = new TaskExecutorContext(
target.write(output.content) 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 package com.jessebrault.ssg
import com.jessebrault.ssg.renderer.RenderContext import com.jessebrault.ssg.task.SpecialPageToHtmlFileTaskFactory
import com.jessebrault.ssg.specialpage.SpecialPage import com.jessebrault.ssg.task.TaskContainer
import com.jessebrault.ssg.task.Output import com.jessebrault.ssg.task.TextToHtmlFileTaskFactory
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.NullCheck
import groovy.transform.TupleConstructor
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.slf4j.Marker import org.slf4j.Marker
import org.slf4j.MarkerFactory import org.slf4j.MarkerFactory
import static com.jessebrault.ssg.util.ExtensionsUtil.stripExtension
@TupleConstructor(includeFields = true)
@NullCheck @NullCheck
@EqualsAndHashCode(includeFields = true)
class SimpleStaticSiteGenerator implements StaticSiteGenerator { class SimpleStaticSiteGenerator implements StaticSiteGenerator {
private static final Logger logger = LoggerFactory.getLogger(SimpleStaticSiteGenerator) private static final Logger logger = LoggerFactory.getLogger(SimpleStaticSiteGenerator)
@ -27,128 +17,25 @@ 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<Output>> generate(Build build) { Result<TaskContainer> 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)
def config = build.config def tasks = new TaskContainer()
def siteSpec = build.siteSpec def diagnostics = []
// Get all texts, templates, parts, and specialPages def textToHtmlFactory = new TextToHtmlFileTaskFactory()
def texts = config.textProviders.collectMany { it.provide() } def textsResult = textToHtmlFactory.getTasks(build)
def templates = config.templatesProviders.collectMany { it.provide() } tasks.addAll(textsResult.get())
def parts = config.partsProviders.collectMany { it.provide() } diagnostics.addAll(textsResult.diagnostics)
def specialPages = config.specialPagesProviders.collectMany { it.provide() }
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 logger.trace(exit, '\n\tdiagnostics: {}\n\ttasks: {}', diagnostics, tasks)
Collection<Diagnostic> diagnostics = [] new Result<>(diagnostics, tasks)
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)
} }
@Override @Override

View File

@ -1,7 +1,7 @@
package com.jessebrault.ssg package com.jessebrault.ssg
import com.jessebrault.ssg.task.Output import com.jessebrault.ssg.task.TaskContainer
interface StaticSiteGenerator { 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.siteSpec = context.siteSpec
it.sourcePath = context.sourcePath it.sourcePath = context.sourcePath
it.targetPath = context.targetPath it.targetPath = context.targetPath
it.tasks = context.tasks
it.text = b.text ? new EmbeddableText( it.text = b.text ? new EmbeddableText(
b.text, b.text,
context.globals, context.globals,

View File

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

View File

@ -1,18 +1,17 @@
package com.jessebrault.ssg.specialpage package com.jessebrault.ssg.specialpage
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
@TupleConstructor(defaults = false) @TupleConstructor(defaults = false)
@NullCheck @NullCheck(includeGenerated = true)
@EqualsAndHashCode @EqualsAndHashCode
class SpecialPage implements Input { final class SpecialPage {
String text final String text
String path final String path
SpecialPageType type final SpecialPageType type
@Override @Override
String toString() { 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 package com.jessebrault.ssg.task
interface Input { interface Input {
String getPath() String getName()
} }

View File

@ -1,20 +1,5 @@
package com.jessebrault.ssg.task package com.jessebrault.ssg.task
import groovy.transform.EqualsAndHashCode interface Output {
import groovy.transform.NullCheck String getName()
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

@ -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 package com.jessebrault.ssg.task
interface Task { interface Task {
Output getOutput() String getName()
OutputMeta getOutputMeta() 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 package com.jessebrault.ssg.task
import org.jetbrains.annotations.Nullable import com.jessebrault.ssg.Build
import com.jessebrault.ssg.Result
interface TaskFactory { interface TaskFactory<T extends Task> {
@Nullable Task getTask(Input input, TaskContext context) Result<TaskCollection<T>> getTasks(Build build)
} }

View File

@ -5,16 +5,16 @@ import groovy.transform.NullCheck
import groovy.transform.TupleConstructor import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false) @TupleConstructor(defaults = false)
@NullCheck @NullCheck(includeGenerated = true)
@EqualsAndHashCode @EqualsAndHashCode
final class OutputMeta { final class TaskType<T extends Task> {
final String sourcePath final String name
final String targetPath final TaskExecutor<T> executor
@Override @Override
String toString() { 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 package com.jessebrault.ssg.text
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
@TupleConstructor(defaults = false) @TupleConstructor(defaults = false)
@NullCheck @NullCheck(includeGenerated = true)
@EqualsAndHashCode @EqualsAndHashCode
class Text implements Input { final class Text {
String text final String text
String path final String path
TextType type final TextType type
@Override @Override
String toString() { 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.GspSpecialPageRenderer
import com.jessebrault.ssg.specialpage.SpecialPageFileSpecialPagesProvider import com.jessebrault.ssg.specialpage.SpecialPageFileSpecialPagesProvider
import com.jessebrault.ssg.specialpage.SpecialPageType 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.GspTemplateRenderer
import com.jessebrault.ssg.template.TemplateFileTemplatesProvider import com.jessebrault.ssg.template.TemplateFileTemplatesProvider
import com.jessebrault.ssg.template.TemplateType import com.jessebrault.ssg.template.TemplateType
@ -18,6 +22,7 @@ import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import static com.jessebrault.ssg.testutil.DiagnosticsUtil.assertEmptyDiagnostics import static com.jessebrault.ssg.testutil.DiagnosticsUtil.assertEmptyDiagnostics
import static com.jessebrault.ssg.testutil.DiagnosticsUtil.getDiagnosticsMessageSupplier
import static org.junit.jupiter.api.Assertions.* import static org.junit.jupiter.api.Assertions.*
class SimpleStaticSiteGeneratorIntegrationTests { class SimpleStaticSiteGeneratorIntegrationTests {
@ -68,11 +73,15 @@ class SimpleStaticSiteGeneratorIntegrationTests {
def result = this.ssg.generate(this.build) def result = this.ssg.generate(this.build)
assertEmptyDiagnostics(result) assertEmptyDiagnostics(result)
assertTrue(result.v2.size() == 1) def tasks = result.get()
assertTrue(tasks.size() == 1)
def p0 = result.v2[0] def t0 = tasks.findAllByType(TextToHtmlFileTask)[0]
assertEquals('test.html', p0.meta.targetPath) assertEquals('test.html', t0.output.htmlPath)
assertEquals('<p><strong>Hello, World!</strong></p>\n', p0.content) 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 @Test
@ -88,11 +97,18 @@ class SimpleStaticSiteGeneratorIntegrationTests {
def result = this.ssg.generate(this.build) def result = this.ssg.generate(this.build)
assertEmptyDiagnostics(result) assertEmptyDiagnostics(result)
assertTrue(result.v2.size() == 1) def tasks = result.get()
assertTrue(tasks.size() == 1)
def p0 = result.v2[0] def t0 = tasks.findAllByType(TextToHtmlFileTask)[0]
assertEquals('nested/nested.html', p0.meta.targetPath) assertEquals('nested/nested.html', t0.output.htmlPath)
assertEquals('<p><strong>Hello, World!</strong></p>\n', p0.content) 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 @Test
@ -105,15 +121,28 @@ class SimpleStaticSiteGeneratorIntegrationTests {
def result = this.ssg.generate(this.build) def result = this.ssg.generate(this.build)
assertEmptyDiagnostics(result) 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' } def taskTypes = new TaskTypeContainer([TextToHtmlFileTask, SpecialPageToHtmlFileTask])
assertNotNull(testPage)
assertEquals('2', testPage.content)
def specialPage = result.v2.find { it.meta.targetPath == 'special.html' } def testPageTask = tasks.findAllByType(TextToHtmlFileTask).find {
assertNotNull(specialPage) it.output.htmlPath == 'test.html'
assertEquals('<p>Hello, World!</p>\n', specialPage.content) }
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 @Test
@ -121,7 +150,7 @@ class SimpleStaticSiteGeneratorIntegrationTests {
new File(this.textsDir, 'test.md').write('Hello, World!') new File(this.textsDir, 'test.md').write('Hello, World!')
def result = this.ssg.generate(this.build) def result = this.ssg.generate(this.build)
assertEmptyDiagnostics(result) 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 org.junit.jupiter.api.Test
import static com.jessebrault.ssg.text.TextMocks.renderableText
import static org.junit.jupiter.api.Assertions.assertEquals import static org.junit.jupiter.api.Assertions.assertEquals
class ExcerptGetterTests { class ExcerptGetterTests {
@ -10,7 +11,7 @@ class ExcerptGetterTests {
@Test @Test
void takesAllIfTextLessThanLimit() { 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) def result = this.excerptGetter.getExcerpt(text, 10)
assertEquals(0, result.v1.size()) assertEquals(0, result.v1.size())
assertEquals('One Two Three Four Five', result.v2) assertEquals('One Two Three Four Five', result.v2)
@ -18,7 +19,7 @@ class ExcerptGetterTests {
@Test @Test
void takesTheLimit() { 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) def result = this.excerptGetter.getExcerpt(text, 2)
assertEquals(0, result.v1.size()) assertEquals(0, result.v1.size())
assertEquals('One Two', result.v2) assertEquals('One Two', result.v2)
@ -26,7 +27,7 @@ class ExcerptGetterTests {
@Test @Test
void worksWithHeading() { 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) def result = this.excerptGetter.getExcerpt(text, 1)
assertEquals(0, result.v1.size()) assertEquals(0, result.v1.size())
assertEquals('Heading', result.v2) assertEquals('Heading', result.v2)
@ -34,7 +35,7 @@ class ExcerptGetterTests {
@Test @Test
void worksWithFrontMatter() { 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) def result = this.excerptGetter.getExcerpt(text, 1)
assertEquals(0, result.v1.size()) assertEquals(0, result.v1.size())
assertEquals('One', result.v2) 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.part.PartType
import com.jessebrault.ssg.renderer.RenderContext import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.tagbuilder.TagBuilder 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.text.EmbeddableTextsCollection
import com.jessebrault.ssg.url.UrlBuilder import com.jessebrault.ssg.url.UrlBuilder
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@ -18,8 +22,11 @@ import org.mockito.junit.jupiter.MockitoExtension
import org.slf4j.Logger import org.slf4j.Logger
import org.slf4j.LoggerFactory 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.DiagnosticsUtil.assertEmptyDiagnostics
import static com.jessebrault.ssg.testutil.RenderContextUtil.getRenderContext 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 com.jessebrault.ssg.text.TextMocks.renderableTextWithPath
import static org.junit.jupiter.api.Assertions.assertEquals import static org.junit.jupiter.api.Assertions.assertEquals
import static org.junit.jupiter.api.Assertions.fail 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 @Test
default void textsAvailable() { default void textsAvailable() {
this.doDslAssertionTest( 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 package com.jessebrault.ssg.testutil
import com.jessebrault.ssg.Diagnostic import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.Result
import com.jessebrault.ssg.text.ExcerptGetter import com.jessebrault.ssg.text.ExcerptGetter
import groovy.transform.stc.ClosureParams import groovy.transform.stc.ClosureParams
import groovy.transform.stc.FirstParam import groovy.transform.stc.FirstParam
@ -25,6 +26,10 @@ class DiagnosticsUtil {
assertTrue(result.v1.isEmpty(), getDiagnosticsMessageSupplier(result.v1)) assertTrue(result.v1.isEmpty(), getDiagnosticsMessageSupplier(result.v1))
} }
static void assertEmptyDiagnostics(Result<?> result) {
assertTrue(!result.hasDiagnostics(), getDiagnosticsMessageSupplier(result.diagnostics))
}
static <E extends Exception> void assertDiagnosticException( static <E extends Exception> void assertDiagnosticException(
Class<E> expectedException, Class<E> expectedException,
Diagnostic diagnostic, Diagnostic diagnostic,

View File

@ -3,6 +3,8 @@ package com.jessebrault.ssg.testutil
import com.jessebrault.ssg.Config import com.jessebrault.ssg.Config
import com.jessebrault.ssg.SiteSpec import com.jessebrault.ssg.SiteSpec
import com.jessebrault.ssg.renderer.RenderContext import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.task.Task
import com.jessebrault.ssg.task.TaskContainer
class RenderContextUtil { class RenderContextUtil {
@ -14,7 +16,9 @@ class RenderContextUtil {
args?.texts as Collection ?: [], args?.texts as Collection ?: [],
args?.parts as Collection ?: [], args?.parts as Collection ?: [],
args?.sourcePath as String ?: '', 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)) when(textRenderer.render(any(), any())).thenReturn(new Tuple2<>([], text))
def frontMatterGetter = mock(FrontMatterGetter) def frontMatterGetter = mock(FrontMatterGetter)
def excerptGetter = mock(ExcerptGetter) def excerptGetter = mock(ExcerptGetter)
new Text('', '', new TextType([], textRenderer, frontMatterGetter, excerptGetter)) new Text(text, '', new TextType([], textRenderer, frontMatterGetter, excerptGetter))
} }
static Text textWithPath(String path) { static Text textWithPath(String path) {
@ -33,7 +33,7 @@ class TextMocks {
when(textRenderer.render(any(), any())).thenReturn(new Tuple2<>([], text)) when(textRenderer.render(any(), any())).thenReturn(new Tuple2<>([], text))
def frontMatterGetter = mock(FrontMatterGetter) def frontMatterGetter = mock(FrontMatterGetter)
def excerptGetter = mock(ExcerptGetter) def excerptGetter = mock(ExcerptGetter)
new Text('', path, new TextType([], textRenderer, frontMatterGetter, excerptGetter)) new Text(text, path, new TextType([], textRenderer, frontMatterGetter, excerptGetter))
} }
} }