Removed lib module.

This commit is contained in:
JesseBrault0709 2023-04-26 15:29:42 +02:00
parent 95f86629f3
commit fdc2c83e99
104 changed files with 1 additions and 3501 deletions

View File

@ -1,22 +0,0 @@
plugins {
id 'ssg.common'
}
repositories {
mavenCentral()
}
dependencies {
// https://mvnrepository.com/artifact/org.apache.groovy/groovy-templates
implementation 'org.apache.groovy:groovy-templates:4.0.9'
// https://mvnrepository.com/artifact/org.commonmark/commonmark
implementation 'org.commonmark:commonmark:0.21.0'
// https://mvnrepository.com/artifact/org.commonmark/commonmark-ext-yaml-front-matter
implementation 'org.commonmark:commonmark-ext-yaml-front-matter:0.21.0'
}
jar {
archivesBaseName = 'ssg-lib'
}

View File

@ -1,24 +0,0 @@
package com.jessebrault.ssg
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
class Build {
String name
Config config
SiteSpec siteSpec
Map globals
File outDir
@Override
String toString() {
"Build(name: ${ this.name }, config: ${ this.config }, siteSpec: ${ this.siteSpec }, " +
"globals: ${ this.globals }, outDir: ${ this.outDir })"
}
}

View File

@ -1,44 +0,0 @@
package com.jessebrault.ssg
import com.jessebrault.ssg.part.PartsProvider
import com.jessebrault.ssg.specialpage.SpecialPagesProvider
import com.jessebrault.ssg.template.TemplatesProvider
import com.jessebrault.ssg.text.TextsProvider
import groovy.transform.Canonical
import groovy.transform.EqualsAndHashCode
import groovy.transform.MapConstructor
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(force = true)
@MapConstructor
@NullCheck
@EqualsAndHashCode
class Config {
Collection<TextsProvider> textProviders
Collection<TemplatesProvider> templatesProviders
Collection<PartsProvider> partsProviders
Collection<SpecialPagesProvider> specialPagesProviders
Config(Config source) {
this.textProviders = [].tap {
addAll(source.textProviders)
}
this.templatesProviders = [].tap {
addAll(source.templatesProviders)
}
this.partsProviders = [].tap {
addAll(source.partsProviders)
}
this.specialPagesProviders = [].tap {
addAll(source.specialPagesProviders)
}
}
String toString() {
"Config(textProviders: ${ this.textProviders }, templatesProviders: ${ this.templatesProviders }, " +
"partsProviders: ${ this.partsProviders }, specialPagesProviders: ${ this.specialPagesProviders })"
}
}

View File

@ -1,18 +0,0 @@
package com.jessebrault.ssg
import groovy.transform.EqualsAndHashCode
import groovy.transform.TupleConstructor
@TupleConstructor
@EqualsAndHashCode
class Diagnostic {
String message
Exception exception
@Override
String toString() {
"Diagnostic(message: ${ this.message }, exception: ${ this.exception })"
}
}

View File

@ -1,28 +0,0 @@
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,53 +0,0 @@
package com.jessebrault.ssg
import com.jessebrault.ssg.task.SpecialPageToHtmlFileTaskFactory
import com.jessebrault.ssg.task.TaskContainer
import com.jessebrault.ssg.task.TaskTypeContainer
import com.jessebrault.ssg.task.TextToHtmlFileTaskFactory
import groovy.transform.NullCheck
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.slf4j.Marker
import org.slf4j.MarkerFactory
@NullCheck
class SimpleStaticSiteGenerator implements StaticSiteGenerator {
private static final Logger logger = LoggerFactory.getLogger(SimpleStaticSiteGenerator)
private static final Marker enter = MarkerFactory.getMarker('ENTER')
private static final Marker exit = MarkerFactory.getMarker('EXIT')
private static final TextToHtmlFileTaskFactory textHtmlFactory = new TextToHtmlFileTaskFactory()
private static final SpecialPageToHtmlFileTaskFactory specialPageHtmlFactory = new SpecialPageToHtmlFileTaskFactory()
@Override
TaskTypeContainer getTaskTypes() {
new TaskTypeContainer([textHtmlFactory.taskType, specialPageHtmlFactory.taskType])
}
@Override
Result<TaskContainer> generate(Build build) {
logger.trace(enter, 'build: {}', build)
logger.info('processing build with name: {}', build.name)
def tasks = new TaskContainer()
def diagnostics = []
def textsResult = textHtmlFactory.getTasks(build)
tasks.addAll(textsResult.get())
diagnostics.addAll(textsResult.diagnostics)
def specialPagesResult = specialPageHtmlFactory.getTasks(build)
tasks.addAll(specialPagesResult.get())
diagnostics.addAll(specialPagesResult.diagnostics)
logger.trace(exit, '\n\tdiagnostics: {}\n\ttasks: {}', diagnostics, tasks)
new Result<>(diagnostics, tasks)
}
@Override
String toString() {
"SimpleStaticSiteGenerator()"
}
}

View File

@ -1,27 +0,0 @@
package com.jessebrault.ssg
import groovy.transform.EqualsAndHashCode
import groovy.transform.MapConstructor
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(force = true, defaults = false)
@MapConstructor
@NullCheck
@EqualsAndHashCode
final class SiteSpec {
String name
String baseUrl
SiteSpec(SiteSpec source) {
this.name = source.name
this.baseUrl = source.baseUrl
}
@Override
String toString() {
"SiteSpec(${ this.name }, ${ this.baseUrl })"
}
}

View File

@ -1,9 +0,0 @@
package com.jessebrault.ssg
import com.jessebrault.ssg.task.TaskContainer
import com.jessebrault.ssg.task.TaskTypeContainer
interface StaticSiteGenerator {
TaskTypeContainer getTaskTypes()
Result<TaskContainer> generate(Build build)
}

View File

@ -1,5 +0,0 @@
package com.jessebrault.ssg
trait WatchableProvider {
File watchableDir
}

View File

@ -1,42 +0,0 @@
package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.Config
import com.jessebrault.ssg.SiteSpec
class BuildClosureDelegate {
String name
Config config
SiteSpec siteSpec
Map globals
File outDir
void config(
@DelegatesTo(value = ConfigClosureDelegate, strategy = Closure.DELEGATE_FIRST)
Closure configClosure
) {
configClosure.setDelegate(new ConfigClosureDelegate(this.config))
configClosure.setResolveStrategy(Closure.DELEGATE_FIRST)
configClosure.run()
}
void siteSpec(
@DelegatesTo(value = SiteSpecClosureDelegate, strategy = Closure.DELEGATE_FIRST)
Closure siteSpecClosure
) {
siteSpecClosure.setDelegate(new SiteSpecClosureDelegate(this.siteSpec))
siteSpecClosure.setResolveStrategy(Closure.DELEGATE_FIRST)
siteSpecClosure.run()
}
void globals(
@DelegatesTo(value = GlobalsClosureDelegate, strategy = Closure.DELEGATE_FIRST)
Closure globalsClosure
) {
def globalsConfigurator = new GlobalsClosureDelegate(this.globals)
globalsClosure.setDelegate(globalsConfigurator)
globalsClosure.setResolveStrategy(Closure.DELEGATE_FIRST)
globalsClosure.run()
}
}

View File

@ -1,42 +0,0 @@
package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.Build
import com.jessebrault.ssg.Config
import com.jessebrault.ssg.SiteSpec
abstract class BuildScriptBase extends Script {
Config defaultConfig
SiteSpec defaultSiteSpec
Map defaultGlobals
Collection<Build> builds = []
protected int currentBuildNumber = 0
void build(
@DelegatesTo(value = BuildClosureDelegate, strategy = Closure.DELEGATE_FIRST)
Closure buildClosure
) {
def buildClosureDelegate = new BuildClosureDelegate().tap {
// Default values for Build properties
name = 'build' + this.currentBuildNumber
config = new Config(defaultConfig)
siteSpec = new SiteSpec(defaultSiteSpec)
globals = new LinkedHashMap(defaultGlobals)
outDir = new File(name)
}
buildClosure.setDelegate(buildClosureDelegate)
buildClosure.setResolveStrategy(Closure.DELEGATE_FIRST)
buildClosure.run()
this.builds << new Build(
buildClosureDelegate.name,
buildClosureDelegate.config,
buildClosureDelegate.siteSpec,
buildClosureDelegate.globals,
buildClosureDelegate.outDir
)
this.currentBuildNumber++
}
}

View File

@ -1,8 +0,0 @@
package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.Build
import com.jessebrault.ssg.Config
interface BuildScriptRunner {
Collection<Build> runBuildScript(String relativePath, Config defaultConfig, Map defaultGlobals)
}

View File

@ -1,43 +0,0 @@
package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.Config
import com.jessebrault.ssg.part.PartType
import com.jessebrault.ssg.specialpage.SpecialPageType
import com.jessebrault.ssg.template.TemplateType
import com.jessebrault.ssg.text.TextType
class ConfigClosureDelegate {
@Delegate
private final Config config
private final Collection<TextType> defaultTextTypes
private final Collection<TemplateType> defaultTemplateTypes
private final Collection<PartType> defaultPartTypes
private final Collection<SpecialPageType> defaultSpecialPageTypes
ConfigClosureDelegate(Config config) {
this.config = config
this.defaultTextTypes = this.config.textProviders.collectMany { it.textTypes }
this.defaultTemplateTypes = this.config.templatesProviders.collectMany { it.templateTypes }
this.defaultPartTypes = this.config.partsProviders.collectMany { it.partTypes }
this.defaultSpecialPageTypes = this.config.specialPagesProviders.collectMany { it.specialPageTypes }
}
Collection<TextType> getDefaultTextTypes() {
this.defaultTextTypes
}
Collection<TemplateType> getDefaultTemplateTypes() {
this.defaultTemplateTypes
}
Collection<PartType> getDefaultPartTypes() {
this.defaultPartTypes
}
Collection<SpecialPageType> getDefaultSpecialPageTypes() {
this.defaultSpecialPageTypes
}
}

View File

@ -1,20 +0,0 @@
package com.jessebrault.ssg.buildscript
import groovy.transform.TupleConstructor
@TupleConstructor(includeFields = true, defaults = false)
class GlobalsClosureDelegate {
private final Map globals
@Override
Object getProperty(String propertyName) {
this.globals[propertyName]
}
@Override
void setProperty(String propertyName, Object newValue) {
this.globals.put(propertyName, newValue)
}
}

View File

@ -1,37 +0,0 @@
package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.Build
import com.jessebrault.ssg.Config
import groovy.transform.NullCheck
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ImportCustomizer
@NullCheck
class GroovyBuildScriptRunner implements BuildScriptRunner {
@Override
Collection<Build> runBuildScript(String relativePath, Config defaultConfig, Map defaultGlobals) {
def engine = new GroovyScriptEngine([new File('.').toURI().toURL()] as URL[])
engine.config = new CompilerConfiguration().tap {
addCompilationCustomizers(new ImportCustomizer().tap {
addStarImports(
'com.jessebrault.ssg',
'com.jessebrault.ssg.part',
'com.jessebrault.ssg.specialpage',
'com.jessebrault.ssg.template',
'com.jessebrault.ssg.text',
'com.jessebrault.ssg.util'
)
})
scriptBaseClass = 'com.jessebrault.ssg.buildscript.BuildScriptBase'
}
def buildScript = engine.createScript(relativePath, new Binding())
assert buildScript instanceof BuildScriptBase
buildScript.defaultConfig = defaultConfig
buildScript.defaultGlobals = defaultGlobals
buildScript.run()
buildScript.builds
}
}

View File

@ -1,14 +0,0 @@
package com.jessebrault.ssg.buildscript
import com.jessebrault.ssg.SiteSpec
class SiteSpecClosureDelegate {
@Delegate
private final SiteSpec siteSpec
SiteSpecClosureDelegate(SiteSpec siteSpec) {
this.siteSpec = siteSpec
}
}

View File

@ -1,84 +0,0 @@
package com.jessebrault.ssg.dsl
import com.jessebrault.ssg.part.EmbeddablePartsMap
import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.tagbuilder.DynamicTagBuilder
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.tagBuilder = new DynamicTagBuilder()
it.targetPath = context.targetPath
it.tasks = context.tasks
it.taskTypes = context.taskTypes
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,54 +0,0 @@
package com.jessebrault.ssg.part
import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.text.Text
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import org.jetbrains.annotations.Nullable
import static java.util.Objects.requireNonNull
@EqualsAndHashCode(includeFields = true)
class EmbeddablePart {
private final Part part
private final RenderContext context
private final Closure onDiagnostics
@Nullable
private final Text text
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.context,
this.text
)
if (result.v1.size() > 0) {
this.onDiagnostics.call(result.v1)
''
} else {
result.v2
}
}
@Override
String toString() {
"EmbeddablePart(part: ${ this.part })"
}
}

View File

@ -1,35 +0,0 @@
package com.jessebrault.ssg.part
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 {
@Delegate
private final Map<String, EmbeddablePart> partsMap = [:]
EmbeddablePartsMap(
Collection<Part> parts,
RenderContext context,
Closure onDiagnostics,
@Nullable Text text = null
) {
requireNonNull(parts)
requireNonNull(context)
requireNonNull(onDiagnostics)
parts.each {
this.put(it.path, new EmbeddablePart(it, context, onDiagnostics, text))
}
}
@Override
String toString() {
"EmbeddablePartsMap(partsMap: ${ this.partsMap })"
}
}

View File

@ -1,56 +0,0 @@
package com.jessebrault.ssg.part
import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.dsl.StandardDslMap
import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.text.EmbeddableText
import com.jessebrault.ssg.text.Text
import groovy.text.GStringTemplateEngine
import groovy.text.TemplateEngine
import groovy.transform.EqualsAndHashCode
import org.jetbrains.annotations.Nullable
@EqualsAndHashCode
class GspPartRenderer implements PartRenderer {
private static final TemplateEngine engine = new GStringTemplateEngine()
@Override
Tuple2<Collection<Diagnostic>, String> render(
Part part,
Map binding,
RenderContext context,
@Nullable Text text
) {
Objects.requireNonNull(part)
Objects.requireNonNull(binding)
Objects.requireNonNull(context)
def diagnostics = []
try {
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<>(
[*diagnostics, new Diagnostic(
"An exception occurred while rendering part ${ part.path }:\n${ e }",
e
)],
''
)
}
}
@Override
String toString() {
"GspPartRenderer()"
}
}

View File

@ -1,21 +0,0 @@
package com.jessebrault.ssg.part
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck
@EqualsAndHashCode
class Part {
String path
PartType type
String text
@Override
String toString() {
"Part(path: ${ this.path }, type: ${ this.type })"
}
}

View File

@ -1,48 +0,0 @@
package com.jessebrault.ssg.part
import com.jessebrault.ssg.provider.AbstractFileCollectionProvider
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import org.jetbrains.annotations.Nullable
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@NullCheck
@EqualsAndHashCode(includeFields = true)
class PartFilePartsProvider extends AbstractFileCollectionProvider<Part> implements PartsProvider {
private static final Logger logger = LoggerFactory.getLogger(PartFilePartsProvider)
private final Collection<PartType> partTypes
PartFilePartsProvider(File partsDir, Collection<PartType> partTypes) {
super(partsDir)
this.partTypes = Objects.requireNonNull(partTypes)
}
private @Nullable PartType getPartType(String extension) {
this.partTypes.find {
it.ids.contains(extension)
}
}
@Override
protected @Nullable Part transformFileToT(File file, String relativePath, String extension) {
def partType = getPartType(extension)
if (!partType) {
logger.warn('there is no PartType for {}, ignoring', relativePath)
}
partType ? new Part(relativePath, partType, file.text) : null
}
@Override
Collection<PartType> getPartTypes() {
this.partTypes
}
@Override
String toString() {
"PartFilePartsProvider(partsDir: ${ this.dir }, partTypes: ${ this.partTypes })"
}
}

View File

@ -1,17 +0,0 @@
package com.jessebrault.ssg.part
import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.text.Text
import org.jetbrains.annotations.Nullable
interface PartRenderer {
Tuple2<Collection<Diagnostic>, String> render(
Part part,
Map binding,
RenderContext context,
@Nullable Text text
)
}

View File

@ -1,20 +0,0 @@
package com.jessebrault.ssg.part
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck
@EqualsAndHashCode
class PartType {
Collection<String> ids
PartRenderer renderer
@Override
String toString() {
"PartType(ids: ${ this.ids }, renderer: ${ this.renderer })"
}
}

View File

@ -1,7 +0,0 @@
package com.jessebrault.ssg.part
import com.jessebrault.ssg.provider.Provider
interface PartsProvider extends Provider<Collection<Part>> {
Collection<PartType> getPartTypes()
}

View File

@ -1,40 +0,0 @@
package com.jessebrault.ssg.provider
import groovy.io.FileType
import org.jetbrains.annotations.Nullable
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import static com.jessebrault.ssg.util.ExtensionsUtil.getExtension
abstract class AbstractFileCollectionProvider<T> implements Provider<Collection<T>>, WithWatchableDir {
private static final Logger logger = LoggerFactory.getLogger(AbstractFileCollectionProvider)
protected final File dir
AbstractFileCollectionProvider(File dir) {
this.dir = Objects.requireNonNull(dir)
this.watchableDir = dir
}
protected abstract @Nullable T transformFileToT(File file, String relativePath, String extension)
@Override
Collection<T> provide() {
if (!this.dir.isDirectory()) {
logger.warn('{} does not exist or is not a directory; skipping', this.dir)
[]
} else {
def ts = []
this.dir.eachFileRecurse(FileType.FILES) {
def t = transformFileToT(it, this.dir.relativePath(it), getExtension(it.path))
if (t) {
ts << t
}
}
ts
}
}
}

View File

@ -1,5 +0,0 @@
package com.jessebrault.ssg.provider
interface Provider<T> {
T provide()
}

View File

@ -1,5 +0,0 @@
package com.jessebrault.ssg.provider
trait WithWatchableDir {
File watchableDir
}

View File

@ -1,26 +0,0 @@
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.TaskContainer
import com.jessebrault.ssg.task.TaskTypeContainer
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
final TaskContainer tasks
final TaskTypeContainer taskTypes
}

View File

@ -1,46 +0,0 @@
package com.jessebrault.ssg.specialpage
import com.jessebrault.ssg.Diagnostic
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
@NullCheck
@EqualsAndHashCode
class GspSpecialPageRenderer implements SpecialPageRenderer {
private static final TemplateEngine engine = new GStringTemplateEngine()
@Override
Tuple2<Collection<Diagnostic>, String> render(
SpecialPage specialPage,
RenderContext context
) {
def diagnostics = []
try {
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
)],
''
)
}
}
@Override
String toString() {
"GspSpecialPageRenderer()"
}
}

View File

@ -1,21 +0,0 @@
package com.jessebrault.ssg.specialpage
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
final class SpecialPage {
final String text
final String path
final SpecialPageType type
@Override
String toString() {
"SpecialPage(path: ${ this.path }, type: ${ this.type })"
}
}

View File

@ -1,50 +0,0 @@
package com.jessebrault.ssg.specialpage
import com.jessebrault.ssg.provider.AbstractFileCollectionProvider
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import org.jetbrains.annotations.Nullable
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@NullCheck
@EqualsAndHashCode(includeFields = true)
class SpecialPageFileSpecialPagesProvider extends AbstractFileCollectionProvider<SpecialPage>
implements SpecialPagesProvider {
private static final Logger logger = LoggerFactory.getLogger(SpecialPageFileSpecialPagesProvider)
private final Collection<SpecialPageType> specialPageTypes
SpecialPageFileSpecialPagesProvider(File specialPagesDir, Collection<SpecialPageType> specialPageTypes) {
super(specialPagesDir)
this.specialPageTypes = Objects.requireNonNull(specialPageTypes)
}
private @Nullable SpecialPageType getSpecialPageType(String extension) {
this.specialPageTypes.find {
it.ids.contains(extension)
}
}
@Override
protected @Nullable SpecialPage transformFileToT(File file, String relativePath, String extension) {
def specialPageType = getSpecialPageType(extension)
if (!specialPageType) {
logger.warn('there is no SpecialPageType for {}, ignoring', relativePath)
}
specialPageType ? new SpecialPage(file.text, relativePath, specialPageType) : null
}
@Override
Collection<SpecialPageType> getSpecialPageTypes() {
this.specialPageTypes
}
@Override
String toString() {
"SpecialPageFileSpecialPagesProvider(specialPagesDir: ${ this.dir }, " +
"specialPageTypes: ${ this.specialPageTypes })"
}
}

View File

@ -1,16 +0,0 @@
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,
RenderContext context
)
}

View File

@ -1,20 +0,0 @@
package com.jessebrault.ssg.specialpage
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck
@EqualsAndHashCode
class SpecialPageType {
Collection<String> ids
SpecialPageRenderer renderer
@Override
String toString() {
"SpecialPageType(ids: ${ this.ids }, renderer: ${ this.renderer })"
}
}

View File

@ -1,7 +0,0 @@
package com.jessebrault.ssg.specialpage
import com.jessebrault.ssg.provider.Provider
interface SpecialPagesProvider extends Provider<Collection<SpecialPage>> {
Collection<SpecialPageType> getSpecialPageTypes()
}

View File

@ -1,77 +0,0 @@
package com.jessebrault.ssg.tagbuilder
import org.codehaus.groovy.runtime.InvokerHelper
class DynamicTagBuilder implements TagBuilder {
@Override
String create(String name) {
"<$name />"
}
@Override
String create(String name, Map<String, ?> attributes) {
def formattedAttributes = attributes.collect {
if (it.value instanceof String) {
it.key + '="' + it.value + '"'
} else if (it.value instanceof Integer) {
it.key + '=' + it.value
} else if (it.value instanceof Boolean && it.value == true) {
it.key
} else {
it.key + '="' + it.value.toString() + '"'
}
}.join(' ')
"<$name $formattedAttributes />"
}
@Override
String create(String name, String body) {
"<$name>$body</$name>"
}
@Override
String create(String name, Map<String, ?> attributes, String body) {
def formattedAttributes = attributes.collect {
if (it.value instanceof String) {
it.key + '="' + it.value + '"'
} else if (it.value instanceof Integer) {
it.key + '=' + it.value
} else if (it.value instanceof Boolean && it.value == true) {
it.key
} else {
it.key + '="' + it.value.toString() + '"'
}
}.join(' ')
"<$name $formattedAttributes>$body</$name>"
}
@Override
Object invokeMethod(String name, Object args) {
def argsList = InvokerHelper.asList(args)
return switch (argsList.size()) {
case 0 -> this.create(name)
case 1 -> {
def arg0 = argsList[0]
if (arg0 instanceof Map) {
this.create(name, arg0)
} else if (arg0 instanceof String) {
this.create(name, arg0)
} else {
throw new MissingMethodException(name, this.class, args, false)
}
}
case 2 -> {
def arg0 = argsList[0]
def arg1 = argsList[1]
if (arg0 instanceof Map && arg1 instanceof String) {
this.create(name, arg0, arg1)
} else {
throw new MissingMethodException(name, this.class, args, false)
}
}
default -> throw new MissingMethodException(name, this.class, args, false)
}
}
}

View File

@ -1,8 +0,0 @@
package com.jessebrault.ssg.tagbuilder
interface TagBuilder {
String create(String name)
String create(String name, Map<String, ?> attributes)
String create(String name, String body)
String create(String name, Map<String, ?> attributes, String body)
}

View File

@ -1,31 +0,0 @@
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

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

View File

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

View File

@ -1,26 +0,0 @@
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 +0,0 @@
package com.jessebrault.ssg.task
interface Input {
String getName()
}

View File

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

View File

@ -1,51 +0,0 @@
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.createParentDirectories()
task.output.file.write(task.output.getContent(
context.allTasks, context.allTypes, context.onDiagnostics
))
}
@Override
String toString() {
'SpecialPageToHtmlFileTaskExecutor()'
}
}
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

@ -1,87 +0,0 @@
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
TaskType<SpecialPageToHtmlFileTask> getTaskType() {
SpecialPageToHtmlFileTask.TYPE
}
@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,7 +0,0 @@
package com.jessebrault.ssg.task
interface Task {
TaskType<? extends Task> getType()
String getName()
void execute(TaskExecutorContext context)
}

View File

@ -1,24 +0,0 @@
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(
TaskType<U> taskType
) {
new TaskCollection<>(this.tasks.findResults {
it.type == taskType ? it : null
})
}
}

View File

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

View File

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

View File

@ -1,23 +0,0 @@
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,9 +0,0 @@
package com.jessebrault.ssg.task
import com.jessebrault.ssg.Build
import com.jessebrault.ssg.Result
interface TaskFactory<T extends Task> {
TaskType<T> getTaskType()
Result<TaskCollection<T>> getTasks(Build build)
}

View File

@ -1,27 +0,0 @@
package com.jessebrault.ssg.task
import com.jessebrault.ssg.provider.Provider
import groovy.io.FileType
final class TaskFactoryManager {
Collection<TaskFactory> getAllFactories(Collection<Provider<File>> factoryPluginDirectoryProviders) {
def factories = []
def gcl = new GroovyClassLoader()
def pluginDirectories = factoryPluginDirectoryProviders.collect { it.provide() }
pluginDirectories.each {
it.eachFileRecurse(FileType.FILES) {
def cl = gcl.parseClass(it)
def constructor = cl.getDeclaredConstructor()
def factory = constructor.newInstance() as TaskFactory
factories << factory
}
}
factories
}
}

View File

@ -1,20 +0,0 @@
package com.jessebrault.ssg.task
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
final class TaskType<T extends Task> {
final String name
final TaskExecutor<T> executor
@Override
String toString() {
"TaskType(${ this.name }, ${ this.executor })"
}
}

View File

@ -1,31 +0,0 @@
package com.jessebrault.ssg.task
final class TaskTypeContainer {
@Delegate
private final Set<TaskType<? extends Task>> taskTypes = []
TaskTypeContainer(Collection<TaskType<? extends Task>> taskTypes) {
if (taskTypes != null) {
this.taskTypes.addAll(taskTypes)
}
}
TaskTypeContainer(TaskTypeContainer taskTypeContainer) {
if (taskTypeContainer != null) {
this.taskTypes.addAll(taskTypeContainer)
}
}
TaskTypeContainer() {}
@Override
TaskType<? extends Task> getProperty(String propertyName) {
def taskType = this.taskTypes.find { it.name == propertyName }
if (!taskType) {
throw new IllegalArgumentException("no such taskType: ${ propertyName }")
}
taskType
}
}

View File

@ -1,14 +0,0 @@
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

@ -1,51 +0,0 @@
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.createParentDirectories()
task.output.file.write(task.output.getContent(
context.allTasks, context.allTypes, context.onDiagnostics
))
}
@Override
String toString() {
'TextToHtmlFileTaskExecutor()'
}
}
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

@ -1,117 +0,0 @@
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
TaskType<TextToHtmlFileTask> getTaskType() {
TextToHtmlFileTask.TYPE
}
@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

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

View File

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

View File

@ -1,49 +0,0 @@
package com.jessebrault.ssg.template
import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.dsl.StandardDslMap
import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.text.Text
import groovy.text.GStringTemplateEngine
import groovy.text.TemplateEngine
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
@NullCheck
@EqualsAndHashCode
class GspTemplateRenderer implements TemplateRenderer {
private static final TemplateEngine engine = new GStringTemplateEngine()
@Override
Tuple2<Collection<Diagnostic>, String> render(
Template template,
Text text,
RenderContext context
) {
def diagnostics = []
try {
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
)],
''
)
}
}
@Override
String toString() {
"GspTemplateRenderer()"
}
}

View File

@ -1,21 +0,0 @@
package com.jessebrault.ssg.template
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck
@EqualsAndHashCode
class Template {
String text
String path
TemplateType type
@Override
String toString() {
"Template(path: ${ this.path }, type: ${ this.type })"
}
}

View File

@ -1,48 +0,0 @@
package com.jessebrault.ssg.template
import com.jessebrault.ssg.provider.AbstractFileCollectionProvider
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import org.jetbrains.annotations.Nullable
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@NullCheck
@EqualsAndHashCode(includeFields = true)
class TemplateFileTemplatesProvider extends AbstractFileCollectionProvider<Template> implements TemplatesProvider {
private static final Logger logger = LoggerFactory.getLogger(TemplateFileTemplatesProvider)
private final Collection<TemplateType> templateTypes
TemplateFileTemplatesProvider(File templatesDir, Collection<TemplateType> templateTypes) {
super(templatesDir)
this.templateTypes = Objects.requireNonNull(templateTypes)
}
private @Nullable TemplateType getType(String extension) {
this.templateTypes.find {
it.ids.contains(extension)
}
}
@Override
protected Template transformFileToT(File file, String relativePath, String extension) {
def templateType = getType(extension)
if (templateType == null) {
logger.warn('there is no TemplateType for template {}, ignoring', relativePath)
}
templateType ? new Template(file.text, relativePath, templateType) : null
}
@Override
Collection<TemplateType> getTemplateTypes() {
this.templateTypes
}
@Override
String toString() {
"TemplateFileTemplatesProvider(templatesDir: ${ this.dir }, templateTypes: ${ this.templateTypes })"
}
}

View File

@ -1,15 +0,0 @@
package com.jessebrault.ssg.template
import com.jessebrault.ssg.Diagnostic
import com.jessebrault.ssg.renderer.RenderContext
import com.jessebrault.ssg.text.Text
interface TemplateRenderer {
Tuple2<Collection<Diagnostic>, String> render(
Template template,
Text text,
RenderContext context
)
}

View File

@ -1,20 +0,0 @@
package com.jessebrault.ssg.template
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck
@EqualsAndHashCode
class TemplateType {
Collection<String> ids
TemplateRenderer renderer
@Override
String toString() {
"TemplateType(ids: ${ this.ids }, renderer: ${ this.renderer })"
}
}

View File

@ -1,7 +0,0 @@
package com.jessebrault.ssg.template
import com.jessebrault.ssg.provider.Provider
interface TemplatesProvider extends Provider<Collection<Template>> {
Collection<TemplateType> getTemplateTypes()
}

View File

@ -1,59 +0,0 @@
package com.jessebrault.ssg.text
import groovy.transform.EqualsAndHashCode
import groovy.transform.Memoized
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(includeFields = true, defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode(includeFields = true)
class EmbeddableText {
private final Text text
private final Map globals
private final Closure onDiagnostics
@Memoized
String render() {
def result = this.text.type.renderer.render(this.text, globals)
if (result.v1.size() > 0) {
this.onDiagnostics.call(result.v1)
''
} else {
result.v2
}
}
@Memoized
FrontMatter getFrontMatter() {
def result = this.text.type.frontMatterGetter.get(this.text)
if (result.v1.size() > 0) {
this.onDiagnostics.call(result.v1)
new FrontMatter(this.text, [:])
} else {
result.v2
}
}
@Memoized
String getExcerpt(int limit) {
def result = this.text.type.excerptGetter.getExcerpt(this.text, limit)
if (result.v1.size() > 0) {
this.onDiagnostics.call(result.v1)
''
} else {
result.v2
}
}
String getPath() {
this.text.path
}
@Override
String toString() {
"EmbeddableText(text: ${ this.text }, globals: ${ this.globals })"
}
}

View File

@ -1,24 +0,0 @@
package com.jessebrault.ssg.text
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
@NullCheck
@EqualsAndHashCode(includeFields = true)
class EmbeddableTextsCollection {
@Delegate
private final Collection<EmbeddableText> embeddableTexts = []
EmbeddableTextsCollection(Collection<Text> texts, Map globals, Closure onDiagnostics) {
Objects.requireNonNull(texts).each {
this << new EmbeddableText(it, globals, onDiagnostics)
}
}
@Override
String toString() {
"EmbeddableTextsCollection(embeddableTexts: ${ this.embeddableTexts })"
}
}

View File

@ -1,7 +0,0 @@
package com.jessebrault.ssg.text
import com.jessebrault.ssg.Diagnostic
interface ExcerptGetter {
Tuple2<Collection<Diagnostic>, String> getExcerpt(Text text, int limit)
}

View File

@ -1,54 +0,0 @@
package com.jessebrault.ssg.text
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@TupleConstructor(includeFields = true, defaults = false)
@NullCheck
@EqualsAndHashCode(includeFields = true)
class FrontMatter {
private static final Logger logger = LoggerFactory.getLogger(FrontMatter)
private final Text text
private final Map<String, List<String>> data
String get(String key) {
if (this.data[key] != null) {
this.data[key][0]
} else {
logger.warn('in {} no entry for key {} in frontMatter, returning empty string', this.text, key)
''
}
}
String getAt(String key) {
this.get(key)
}
List<String> getList(String key) {
if (data[key] != null) {
data[key]
} else {
logger.warn('in {} no entry for key {} in frontMatter, returning empty list: {}', this.text, key)
[]
}
}
Optional<String> find(String key) {
Optional.ofNullable(this.data[key]?[0])
}
Optional<List<String>> findList(String key) {
Optional.ofNullable(this.data[key])
}
@Override
String toString() {
"FrontMatter(text: ${ this.text }, data: ${ this.data })"
}
}

View File

@ -1,7 +0,0 @@
package com.jessebrault.ssg.text
import com.jessebrault.ssg.Diagnostic
interface FrontMatterGetter {
Tuple2<Collection<Diagnostic>, FrontMatter> get(Text text)
}

View File

@ -1,54 +0,0 @@
package com.jessebrault.ssg.text
import com.jessebrault.ssg.Diagnostic
import org.commonmark.ext.front.matter.YamlFrontMatterExtension
import org.commonmark.node.AbstractVisitor
import org.commonmark.parser.Parser
class MarkdownExcerptGetter implements ExcerptGetter {
private static class ExcerptVisitor extends AbstractVisitor {
final int limit
List<String> words = []
ExcerptVisitor(int limit) {
this.limit = limit
}
@Override
void visit(org.commonmark.node.Text text) {
if (this.words.size() <= limit) {
def textWords = text.literal.split('\\s+').toList()
def taken = textWords.take(this.limit - this.words.size())
this.words.addAll(taken)
}
}
String getResult() {
this.words.take(this.limit).join(' ')
}
}
private static final Parser parser = Parser.builder()
.extensions([YamlFrontMatterExtension.create()])
.build()
@Override
Tuple2<Collection<Diagnostic>, String> getExcerpt(Text text, int limit) {
try {
def node = parser.parse(text.text)
def visitor = new ExcerptVisitor(limit)
node.accept(visitor)
new Tuple2<>([], visitor.result)
} catch (Exception e) {
def diagnostic = new Diagnostic(
"There was an exception while getting the excerpt for ${ text }:\n${ e }",
e
)
new Tuple2<>([diagnostic], '')
}
}
}

View File

@ -1,35 +0,0 @@
package com.jessebrault.ssg.text
import com.jessebrault.ssg.Diagnostic
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import org.commonmark.ext.front.matter.YamlFrontMatterExtension
import org.commonmark.ext.front.matter.YamlFrontMatterVisitor
import org.commonmark.parser.Parser
@NullCheck
@EqualsAndHashCode
class MarkdownFrontMatterGetter implements FrontMatterGetter {
private static final Parser parser = Parser.builder()
.extensions([YamlFrontMatterExtension.create()])
.build()
@Override
Tuple2<Collection<Diagnostic>, FrontMatter> get(Text text) {
try {
def node = parser.parse(text.text)
def v = new YamlFrontMatterVisitor()
node.accept(v)
new Tuple2([], new FrontMatter(text, v.data))
} catch (Exception e) {
new Tuple2<>([new Diagnostic("An exception occured while parsing frontMatter for ${ text.path }:\n${ e }", e)], new FrontMatter(text, [:]))
}
}
@Override
String toString() {
"MarkdownFrontMatterGetter()"
}
}

View File

@ -1,33 +0,0 @@
package com.jessebrault.ssg.text
import com.jessebrault.ssg.Diagnostic
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import org.commonmark.ext.front.matter.YamlFrontMatterExtension
import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer
@NullCheck
@EqualsAndHashCode
class MarkdownTextRenderer implements TextRenderer {
private static final Parser parser = Parser.builder()
.extensions([YamlFrontMatterExtension.create()])
.build()
private static final HtmlRenderer htmlRenderer = HtmlRenderer.builder().build()
@Override
Tuple2<Collection<Diagnostic>, String> render(Text text, Map globals) {
try {
new Tuple2<>([], htmlRenderer.render(parser.parse(text.text)))
} catch (Exception e) {
new Tuple2<>([new Diagnostic("There was an exception while rendering ${ text.path }:\n${ e }", e)], '')
}
}
@Override
String toString() {
"MarkdownTextRenderer()"
}
}

View File

@ -1,21 +0,0 @@
package com.jessebrault.ssg.text
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck(includeGenerated = true)
@EqualsAndHashCode
final class Text {
final String text
final String path
final TextType type
@Override
String toString() {
"Text(path: ${ this.path }, type: ${ this.type })"
}
}

View File

@ -1,48 +0,0 @@
package com.jessebrault.ssg.text
import com.jessebrault.ssg.provider.AbstractFileCollectionProvider
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import org.jetbrains.annotations.Nullable
import org.slf4j.Logger
import org.slf4j.LoggerFactory
@NullCheck
@EqualsAndHashCode(includeFields = true)
class TextFileTextsProvider extends AbstractFileCollectionProvider<Text> implements TextsProvider {
private static final Logger logger = LoggerFactory.getLogger(TextFileTextsProvider)
private final Collection<TextType> textTypes
TextFileTextsProvider(File textsDir, Collection<TextType> textTypes) {
super(textsDir)
this.textTypes = Objects.requireNonNull(textTypes)
}
private TextType getTextType(String extension) {
this.textTypes.find {
it.ids.contains(extension)
}
}
@Override
protected @Nullable Text transformFileToT(File file, String relativePath, String extension) {
def textType = getTextType(extension)
if (!textType) {
logger.warn('no TextType for text {}, ignoring', file.path)
}
textType ? new Text(file.text, relativePath, textType) : null
}
@Override
Collection<TextType> getTextTypes() {
this.textTypes
}
@Override
String toString() {
"TextFileTextsProvider(textsDir: ${ this.dir }, textTypes: ${ this.textTypes })"
}
}

View File

@ -1,7 +0,0 @@
package com.jessebrault.ssg.text
import com.jessebrault.ssg.Diagnostic
interface TextRenderer {
Tuple2<Collection<Diagnostic>, String> render(Text text, Map globals)
}

View File

@ -1,22 +0,0 @@
package com.jessebrault.ssg.text
import groovy.transform.EqualsAndHashCode
import groovy.transform.NullCheck
import groovy.transform.TupleConstructor
@TupleConstructor(defaults = false)
@NullCheck
@EqualsAndHashCode
class TextType {
Collection<String> ids
TextRenderer renderer
FrontMatterGetter frontMatterGetter
ExcerptGetter excerptGetter
@Override
String toString() {
"TextType(ids: ${ this.ids }, renderer: ${ this.renderer }, frontMatterGetter: ${ this.renderer }, excerptGetter: ${ this.excerptGetter })"
}
}

View File

@ -1,7 +0,0 @@
package com.jessebrault.ssg.text
import com.jessebrault.ssg.provider.Provider
interface TextsProvider extends Provider<Collection<Text>> {
Collection<TextType> getTextTypes()
}

View File

@ -1,37 +0,0 @@
package com.jessebrault.ssg.url
import java.nio.file.Path
class PathBasedUrlBuilder implements UrlBuilder {
private final String absolute
private final String baseUrl
private final Path fromDirectory
PathBasedUrlBuilder(String targetPath, String baseUrl) {
this.absolute = baseUrl + '/' + targetPath
this.baseUrl = baseUrl
def fromFilePath = Path.of(targetPath)
if (fromFilePath.parent) {
this.fromDirectory = fromFilePath.parent
} else {
this.fromDirectory = Path.of('')
}
}
@Override
String getAbsolute() {
this.absolute
}
@Override
String absolute(String to) {
this.baseUrl + '/' + to
}
@Override
String relative(String to) {
this.fromDirectory.relativize(Path.of(to)).toString()
}
}

View File

@ -1,7 +0,0 @@
package com.jessebrault.ssg.url
interface UrlBuilder {
String getAbsolute()
String absolute(String to)
String relative(String to)
}

View File

@ -1,24 +0,0 @@
package com.jessebrault.ssg.util
import java.util.regex.Pattern
class ExtensionsUtil {
private static final Pattern stripExtensionPattern = ~/(.+)\..+$/
private static final Pattern getExtensionPattern = ~/.+(\..+)$/
static String stripExtension(String path) {
def m = stripExtensionPattern.matcher(path)
m.matches() ? m.group(1) : path
}
static String getExtension(String path) {
def m = getExtensionPattern.matcher(path)
if (m.matches()) {
m.group(1)
} else {
throw new IllegalArgumentException("cannot get extension for path: ${ path }")
}
}
}

View File

@ -1,156 +0,0 @@
package com.jessebrault.ssg
import com.jessebrault.ssg.part.GspPartRenderer
import com.jessebrault.ssg.part.PartFilePartsProvider
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
import com.jessebrault.ssg.text.MarkdownExcerptGetter
import com.jessebrault.ssg.text.MarkdownFrontMatterGetter
import com.jessebrault.ssg.text.MarkdownTextRenderer
import com.jessebrault.ssg.text.TextFileTextsProvider
import com.jessebrault.ssg.text.TextType
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 {
private File partsDir
private File templatesDir
private File textsDir
private File specialPagesDir
private Build build
private StaticSiteGenerator ssg
@BeforeEach
void beforeEach() {
this.textsDir = File.createTempDir()
this.templatesDir = File.createTempDir()
this.partsDir = File.createTempDir()
this.specialPagesDir = File.createTempDir()
def markdownTextType = new TextType(['.md'], new MarkdownTextRenderer(), new MarkdownFrontMatterGetter(), new MarkdownExcerptGetter())
def gspTemplateType = new TemplateType(['.gsp'], new GspTemplateRenderer())
def gspPartType = new PartType(['.gsp'], new GspPartRenderer())
def gspSpecialPageType = new SpecialPageType(['.gsp'], new GspSpecialPageRenderer())
def textsProvider = new TextFileTextsProvider(this.textsDir, [markdownTextType])
def templatesProvider = new TemplateFileTemplatesProvider(this.templatesDir, [gspTemplateType])
def partsProvider = new PartFilePartsProvider(this.partsDir, [gspPartType])
def specialPagesProvider = new SpecialPageFileSpecialPagesProvider(this.specialPagesDir, [gspSpecialPageType])
def config = new Config(
textProviders: [textsProvider],
templatesProviders: [templatesProvider],
partsProviders: [partsProvider],
specialPagesProviders: [specialPagesProvider]
)
def siteSpec = new SiteSpec('Test Site', 'https://test.com')
def globals = [:]
this.build = new Build('testBuild', config, siteSpec, globals, new File('build'))
this.ssg = new SimpleStaticSiteGenerator()
}
@Test
void simple() {
new File(this.textsDir, 'test.md').write('---\ntemplate: test.gsp\n---\n**Hello, World!**')
new File(this.templatesDir, 'test.gsp').write('<%= text.render() %>')
def result = this.ssg.generate(this.build)
assertEmptyDiagnostics(result)
def tasks = result.get()
assertTrue(tasks.size() == 1)
def t0 = tasks.findAllByType(TextToHtmlFileTask.TYPE)[0]
assertEquals('test.html', t0.output.htmlPath)
def contentResult = t0.output.getContent(tasks, new TaskTypeContainer([TextToHtmlFileTask.TYPE])) { Collection<Diagnostic> diagnostics ->
fail(getDiagnosticsMessageSupplier(diagnostics))
}
assertEquals('<p><strong>Hello, World!</strong></p>\n', contentResult)
}
@Test
void nested() {
new FileTreeBuilder(this.textsDir).with {
dir('nested') {
file('nested.md', '---\ntemplate: nested.gsp\n---\n**Hello, World!**')
}
}
new File(this.templatesDir, 'nested.gsp').write('<%= text.render() %>')
def result = this.ssg.generate(this.build)
assertEmptyDiagnostics(result)
def tasks = result.get()
assertTrue(tasks.size() == 1)
def t0 = tasks.findAllByType(TextToHtmlFileTask.TYPE)[0]
assertEquals('nested/nested.html', t0.output.htmlPath)
def contentResult = t0.output.getContent(
tasks,
new TaskTypeContainer([TextToHtmlFileTask.TYPE])
) { Collection<Diagnostic> diagnostics ->
fail(getDiagnosticsMessageSupplier(diagnostics))
}
assertEquals('<p><strong>Hello, World!</strong></p>\n', contentResult)
}
@Test
void outputsSpecialPage() {
new FileTreeBuilder(this.specialPagesDir)
.file('special.gsp', $/<%= texts.find { it.path == 'test.md' }.render() %>/$)
new FileTreeBuilder(this.templatesDir).file('template.gsp', '<%= 1 + 1 %>')
new FileTreeBuilder(this.textsDir).file('test.md', '---\ntemplate: template.gsp\n---\nHello, World!')
def result = this.ssg.generate(this.build)
assertEmptyDiagnostics(result)
def tasks = result.get()
assertEquals(2, tasks.size())
def taskTypes = new TaskTypeContainer([TextToHtmlFileTask.TYPE, SpecialPageToHtmlFileTask.TYPE])
def testPageTask = tasks.findAllByType(TextToHtmlFileTask.TYPE).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.TYPE).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
void doesNotGenerateIfNoTemplateInFrontMatter() {
new File(this.textsDir, 'test.md').write('Hello, World!')
def result = this.ssg.generate(this.build)
assertEmptyDiagnostics(result)
assertEquals(0, result.get().size())
}
}

View File

@ -1,110 +0,0 @@
package com.jessebrault.ssg.part
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.junit.jupiter.MockitoExtension
import static com.jessebrault.ssg.testutil.DiagnosticsUtil.assertDiagnosticException
import static com.jessebrault.ssg.testutil.RenderContextUtil.getRenderContext
import static com.jessebrault.ssg.text.TextMocks.renderableText
import static org.junit.jupiter.api.Assertions.assertEquals
@ExtendWith(MockitoExtension)
class GspPartRendererTests implements StandardDslConsumerTests {
private final PartRenderer renderer = new GspPartRenderer()
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
)
}
@Override
Tuple2<Collection<Diagnostic>, String> render(String scriptlet, RenderContext context) {
this.doRender(scriptlet, context)
}
@Test
void rendersWithBinding() {
this.checkResult(
'Hello, World!',
this.doRender('<%= binding.greeting %>', getRenderContext(), [greeting: 'Hello, World!'])
)
}
@Test
void textAvailable() {
this.checkResult('Hello, World!', this.renderer.render(
new Part('', new PartType([], this.renderer), '<%= text.render() %>'),
[:],
getRenderContext(),
renderableText('Hello, World!')
))
}
@Test
void nestedPartDiagnosticBubblesUp() {
def nestedProblemPart = new Part(
'nestedProblem.gsp',
new PartType([], this.renderer),
'<% throw new RuntimeException() %>'
)
def callerPart = new Part(
'caller.gsp',
null,
'<% parts["nestedProblem.gsp"].render() %>'
)
def r = this.renderer.render(
callerPart,
[:],
getRenderContext(parts: [callerPart, nestedProblemPart]),
null
)
assertEquals(1, r.v1.size())
assertDiagnosticException(RuntimeException, r.v1[0])
assertEquals('', r.v2)
}
@Test
void nestedPartIsBlankWhenThrowingExceptionButCallerRendered() {
def nestedProblemPart = new Part(
'nestedProblem.gsp',
new PartType([], this.renderer),
'<% throw new RuntimeException("nested problem exception") %>'
)
def callerPart = new Part(
'caller.gsp',
null,
'Hello, World!<% parts["nestedProblem.gsp"].render() %>'
)
def r = this.renderer.render(
callerPart,
[:],
getRenderContext(parts: [callerPart, nestedProblemPart]),
null
)
assertEquals(1, r.v1.size())
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,62 +0,0 @@
package com.jessebrault.ssg.part
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import static org.junit.jupiter.api.Assertions.assertEquals
import static org.mockito.Mockito.mock
class PartFilePartsProviderTests {
private static final PartType gspPartType = new PartType(['.gsp'], mock(PartRenderer))
private File partsDir
private PartsProvider partsProvider
@BeforeEach
void beforeEach() {
this.partsDir = File.createTempDir()
partsProvider = new PartFilePartsProvider(this.partsDir, [gspPartType])
}
@Test
void findsPart() {
new FileTreeBuilder(this.partsDir).file('testPart.gsp') {
write('Hello <%= name %>!')
}
def r = this.partsProvider.provide()
assertEquals(1, r.size())
def p0 = r[0]
assertEquals('testPart.gsp', p0.path)
assertEquals(gspPartType, p0.type)
assertEquals('Hello <%= name %>!', p0.text)
}
@Test
void findsNested() {
new FileTreeBuilder(this.partsDir).dir('nested') {
file('testPart.gsp') {
write('Hello, World!')
}
}
def r = this.partsProvider.provide()
assertEquals(1, r.size())
def p0 = r[0]
assertEquals('nested/testPart.gsp', p0.path)
assertEquals(gspPartType, p0.type)
assertEquals('Hello, World!', p0.text)
}
@Test
void ignoresUnsupportedFile() {
new FileTreeBuilder(this.partsDir).file('.ignored') {
write 'Ignored!'
}
def r = this.partsProvider.provide()
assertEquals(0, r.size())
}
}

View File

@ -1,22 +0,0 @@
package com.jessebrault.ssg.specialpage
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.junit.jupiter.MockitoExtension
@ExtendWith(MockitoExtension)
class GspSpecialPageRendererTests implements StandardDslConsumerTests {
private final SpecialPageRenderer renderer = new GspSpecialPageRenderer()
@Override
Tuple2<Collection<Diagnostic>, String> render(String scriptlet, RenderContext context) {
this.renderer.render(
new SpecialPage(scriptlet, '', new SpecialPageType([], this.renderer)),
context
)
}
}

View File

@ -1,57 +0,0 @@
package com.jessebrault.ssg.specialpage
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import static org.junit.jupiter.api.Assertions.assertEquals
import static org.mockito.Mockito.mock
class SpecialPageFileSpecialPagesProviderTests {
private static final SpecialPageType gspType = new SpecialPageType(['.gsp'], mock(SpecialPageRenderer))
private File specialPagesDir
private SpecialPagesProvider specialPagesProvider
@BeforeEach
void beforeEach() {
this.specialPagesDir = File.createTempDir()
this.specialPagesProvider = new SpecialPageFileSpecialPagesProvider(this.specialPagesDir, [gspType])
}
@Test
void findsSpecialPage() {
new FileTreeBuilder(this.specialPagesDir)
.file('test.gsp', '<%= "Hello, World!" %>')
def r = this.specialPagesProvider.provide()
assertEquals(1, r.size())
def f0 = r[0]
assertEquals('test.gsp', f0.path)
assertEquals('<%= "Hello, World!" %>', f0.text)
assertEquals(gspType, f0.type)
}
@Test
void findsNestedSpecialPage() {
new FileTreeBuilder(this.specialPagesDir).dir('nested') {
file('nested.gsp', '<%= "Hello, World!" %>')
}
def r = this.specialPagesProvider.provide()
assertEquals(1, r.size())
def f0 = r[0]
assertEquals('nested/nested.gsp', f0.path)
assertEquals('<%= "Hello, World!" %>', f0.text)
assertEquals(gspType, f0.type)
}
@Test
void ignoresUnsupportedFile() {
new FileTreeBuilder(this.specialPagesDir).file('.ignored', 'Ignored!')
def r = this.specialPagesProvider.provide()
assertEquals(0, r.size())
}
}

View File

@ -1,54 +0,0 @@
package com.jessebrault.ssg.tagbuilder
import org.junit.jupiter.api.Test
import static org.junit.jupiter.api.Assertions.assertEquals
class TagBuilderTests {
private final TagBuilder tagBuilder = new DynamicTagBuilder()
@Test
void simple() {
assertEquals('<test />', this.tagBuilder.create('test'))
}
@Test
void withAttributes() {
assertEquals('<test test="abc" />', this.tagBuilder.create('test', [test: 'abc']))
}
@Test
void withBody() {
assertEquals('<test>test</test>', this.tagBuilder.create('test', 'test'))
}
@Test
void withAttributesAndBody() {
assertEquals(
'<test test="abc">test</test>',
this.tagBuilder.create('test', [test: 'abc'], 'test')
)
}
@Test
void dynamicSimple() {
assertEquals('<test />', this.tagBuilder.test())
}
@Test
void dynamicWithAttributes() {
assertEquals('<test test="abc" />', this.tagBuilder.test([test: 'abc']))
}
@Test
void dynamicWithBody() {
assertEquals('<test>test</test>', this.tagBuilder.test('test'))
}
@Test
void dynamicWithAttributesAndBody() {
assertEquals('<test test="abc">test</test>', this.tagBuilder.test([test: 'abc'], 'test'))
}
}

View File

@ -1,47 +0,0 @@
package com.jessebrault.ssg.template
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.junit.jupiter.MockitoExtension
import static com.jessebrault.ssg.testutil.DiagnosticsUtil.getDiagnosticsMessageSupplier
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
@ExtendWith(MockitoExtension)
class GspTemplateRendererTests implements StandardDslConsumerTests {
private final TemplateRenderer renderer = new GspTemplateRenderer()
private Tuple2<Collection<Diagnostic>, String> doRender(String scriptlet, Text text, RenderContext context) {
this.renderer.render(new Template(scriptlet, '', null), text, context)
}
@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,
renderableText('Hello, World!'),
getRenderContext()
)
assertTrue(r.v1.isEmpty(), getDiagnosticsMessageSupplier(r.v1))
assertEquals('Hello, World!', r.v2)
}
}

View File

@ -1,60 +0,0 @@
package com.jessebrault.ssg.template
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import static org.junit.jupiter.api.Assertions.assertEquals
import static org.mockito.Mockito.mock
class TemplateFileTemplatesProviderTests {
private static final TemplateType gspType = new TemplateType(['.gsp'], mock(TemplateRenderer))
private File templatesDir
private TemplatesProvider templatesProvider
@BeforeEach
void beforeEach() {
this.templatesDir = File.createTempDir()
this.templatesProvider = new TemplateFileTemplatesProvider(this.templatesDir, [gspType])
}
@Test
void findsTemplate() {
new File(this.templatesDir, 'test.gsp').write('<% out << text %>')
def r = this.templatesProvider.provide()
assertEquals(1, r.size())
def t0 = r[0]
assertEquals('test.gsp', t0.path)
assertEquals('<% out << text %>', t0.text)
assertEquals(gspType, t0.type)
}
@Test
void findsNestedTemplate() {
new FileTreeBuilder(this.templatesDir).with {
dir('nested') {
file('nested.gsp', '<%= text %>')
}
}
def r = this.templatesProvider.provide()
assertEquals(1, r.size())
def t0 = r[0]
assertEquals('nested/nested.gsp', t0.path)
assertEquals('<%= text %>', t0.text)
assertEquals(gspType, t0.type)
}
@Test
void ignoresUnsupportedFile() {
new File(this.templatesDir, '.ignored').with {
write('Ignored!')
}
def r = this.templatesProvider.provide()
assertEquals(0, r.size())
}
}

View File

@ -1,44 +0,0 @@
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 {
ExcerptGetter excerptGetter = new MarkdownExcerptGetter()
@Test
void takesAllIfTextLessThanLimit() {
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)
}
@Test
void takesTheLimit() {
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)
}
@Test
void worksWithHeading() {
def text = renderableText('# Heading\nOne Two Three')
def result = this.excerptGetter.getExcerpt(text, 1)
assertEquals(0, result.v1.size())
assertEquals('Heading', result.v2)
}
@Test
void worksWithFrontMatter() {
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

@ -1,61 +0,0 @@
package com.jessebrault.ssg.text
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import static org.junit.jupiter.api.Assertions.assertEquals
import static org.mockito.Mockito.mock
class TextFileTextsProviderTests {
private static final TextType markdownType = new TextType(
['.md'],
mock(TextRenderer),
mock(FrontMatterGetter),
mock(ExcerptGetter)
)
private File textsDir
private TextsProvider textsProvider
@BeforeEach
void beforeEach() {
this.textsDir = File.createTempDir()
this.textsProvider = new TextFileTextsProvider(this.textsDir, [markdownType])
}
@Test
void findsFile() {
new FileTreeBuilder(this.textsDir).file('test.md', '**Hello, World!**')
def r = this.textsProvider.provide()
assertEquals(1, r.size())
def f0 = r[0]
assertEquals('test.md', f0.path)
assertEquals('**Hello, World!**', f0.text)
assertEquals(markdownType, f0.type)
}
@Test
void findsNestedFiles() {
new FileTreeBuilder(this.textsDir).dir('nested') {
file('nested.md', '**Hello!**')
}
def r = this.textsProvider.provide()
assertEquals(1, r.size())
def f0 = r[0]
assertEquals('nested/nested.md', f0.path)
assertEquals('**Hello!**', f0.text)
assertEquals(markdownType, f0.type)
}
@Test
void ignoresUnsupportedFile() {
new FileTreeBuilder(this.textsDir).file('.ignored', 'Ignored!')
def r = this.textsProvider.provide()
assertEquals(0, r.size())
}
}

View File

@ -1,10 +0,0 @@
package com.jessebrault.ssg.url
class PathBasedUrlBuilderTests extends AbstractUrlBuilderTests {
@Override
protected UrlBuilder getUrlBuilder(String targetPath, String baseUrl) {
new PathBasedUrlBuilder(targetPath, baseUrl)
}
}

View File

@ -1,49 +0,0 @@
package com.jessebrault.ssg.util
import org.junit.jupiter.api.Test
import static com.jessebrault.ssg.util.ExtensionsUtil.stripExtension
import static com.jessebrault.ssg.util.ExtensionsUtil.getExtension
import static org.junit.jupiter.api.Assertions.assertEquals
class ExtensionsUtilTests {
static class StripExtensionTests {
@Test
void simple() {
assertEquals('test', stripExtension('test.txt'))
}
@Test
void withSlashes() {
assertEquals('test/test', stripExtension('test/test.txt'))
}
@Test
void withMultipleExtensions() {
assertEquals('test.txt', stripExtension('test.txt.html'))
}
}
static class GetExtensionTests {
@Test
void simple() {
assertEquals('.txt', getExtension('test.txt'))
}
@Test
void withSlashes() {
assertEquals('.txt', getExtension('test/test.txt'))
}
@Test
void withMultipleExtensions() {
assertEquals('.txt', getExtension('test.test.txt'))
}
}
}

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Configuration name="ssg" status="WARN">
<Appenders>
<Console name="standard" target="SYSTEM_OUT">
<PatternLayout>
<MarkerPatternSelector defaultPattern="%highlight{%-5level} %logger{1}: %msg%n%ex">
<PatternMatch key="FLOW" pattern="%highlight{%-5level} %logger{1}: %markerSimpleName %msg%n%ex" />
</MarkerPatternSelector>
</PatternLayout>
</Console>
</Appenders>
<Loggers>
<Root level="trace">
<AppenderRef ref="standard" />
</Root>
</Loggers>
</Configuration>

View File

@ -1,28 +0,0 @@
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

@ -1,240 +0,0 @@
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.task.HtmlFileOutput
import com.jessebrault.ssg.task.SpecialPageToHtmlFileTask
import com.jessebrault.ssg.task.TaskContainer
import com.jessebrault.ssg.task.TaskTypeContainer
import com.jessebrault.ssg.task.TextToHtmlFileTask
import com.jessebrault.ssg.task.TextToHtmlFileTaskFactory
import com.jessebrault.ssg.text.EmbeddableTextsCollection
import com.jessebrault.ssg.url.UrlBuilder
import net.bytebuddy.implementation.bytecode.Throw
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.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
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) {
Tuple2<Collection<Diagnostic>, String> result = null
try {
result = this.render(scriptlet, context ?: getRenderContext())
} catch (Throwable e) {
fail(e)
}
assertEmptyDiagnostics(result)
}
@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 tagBuilder && 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 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]),
taskTypes: new TaskTypeContainer([TextToHtmlFileTask.TYPE])
)
)
}
@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]),
taskTypes: new TaskTypeContainer([TextToHtmlFileTask.TYPE, SpecialPageToHtmlFileTask.TYPE])
)
)
}
@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

@ -1,16 +0,0 @@
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

@ -1,19 +0,0 @@
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

@ -1,19 +0,0 @@
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,47 +0,0 @@
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
import static org.junit.jupiter.api.Assertions.assertInstanceOf
import static org.junit.jupiter.api.Assertions.assertTrue
class DiagnosticsUtil {
static Closure<String> getDiagnosticsMessageSupplier(Collection<Diagnostic> diagnostics) {
return {
diagnostics.collect {
def writer = new StringWriter()
it.exception.printStackTrace(new PrintWriter(writer))
def stackTrace = writer.toString()
"$it.message\n$stackTrace"
}.join('\n')
}
}
static void assertEmptyDiagnostics(Tuple2<Collection<Diagnostic>, ?> result) {
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,
@ClosureParams(FirstParam.FirstGenericType)
Closure<Void> additionalAssertions = null
) {
assertInstanceOf(expectedException, diagnostic.exception, {
"Incorrect diagnostic exception class; message: ${ diagnostic.message }"
})
if (additionalAssertions) {
additionalAssertions(diagnostic.exception)
}
}
}

Some files were not shown because too many files have changed in this diff Show More