Major work for v0.2.0; moved from lib to api; tests all working.
This commit is contained in:
parent
0ef4a3aafd
commit
db5d00bdca
5
TODO.md
5
TODO.md
@ -15,5 +15,10 @@ Here will be kept all of the various todos for this project, organized by releas
|
|||||||
// as well as some other information, perhaps such as the Type, extension, *etc.*
|
// as well as some other information, perhaps such as the Type, extension, *etc.*
|
||||||
```
|
```
|
||||||
- [ ] Add `extensionUtil` object to dsl.
|
- [ ] Add `extensionUtil` object to dsl.
|
||||||
|
- [ ] Investigate imports, including static, in scripts
|
||||||
|
- [ ] Get rid of `taskTypes` DSL, replace with static import of task types to scripts
|
||||||
|
- [ ] Plan out plugin system such that we can create custom providers of texts, data, etc.
|
||||||
|
- [ ] Plan out `data` models DSL
|
||||||
|
- [ ] Provide a way to override `ssgBuilds` variables from the cli.
|
||||||
|
|
||||||
### Fix
|
### Fix
|
||||||
|
22
api/build.gradle
Normal file
22
api/build.gradle
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
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-api'
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package com.jessebrault.ssg
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.buildscript.Build
|
||||||
|
import com.jessebrault.ssg.task.Task
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
|
||||||
|
interface BuildTasksConverter {
|
||||||
|
Result<Collection<Task>> convert(Build buildScriptResult)
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.jessebrault.ssg
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.buildscript.Build
|
||||||
|
import com.jessebrault.ssg.task.Task
|
||||||
|
import com.jessebrault.ssg.task.TaskSpec
|
||||||
|
import com.jessebrault.ssg.util.Diagnostic
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
|
||||||
|
final class SimpleBuildTasksConverter implements BuildTasksConverter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Result<Collection<Task>> convert(Build buildScriptResult) {
|
||||||
|
def taskSpec = new TaskSpec(
|
||||||
|
buildScriptResult.name,
|
||||||
|
buildScriptResult.outputDirFunction.apply(buildScriptResult).file,
|
||||||
|
buildScriptResult.siteSpec,
|
||||||
|
buildScriptResult.globals
|
||||||
|
)
|
||||||
|
Collection<Task> tasks = []
|
||||||
|
Collection<Diagnostic> diagnostics = []
|
||||||
|
|
||||||
|
buildScriptResult.taskFactorySpecs.each {
|
||||||
|
def factory = it.supplier.get()
|
||||||
|
it.configureClosures.each { it(factory) }
|
||||||
|
def result = factory.getTasks(taskSpec)
|
||||||
|
diagnostics.addAll(result.diagnostics)
|
||||||
|
tasks.addAll(result.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.of(diagnostics, tasks)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
35
api/src/main/groovy/com/jessebrault/ssg/SiteSpec.groovy
Normal file
35
api/src/main/groovy/com/jessebrault/ssg/SiteSpec.groovy
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package com.jessebrault.ssg
|
||||||
|
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@TupleConstructor(defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class SiteSpec {
|
||||||
|
|
||||||
|
static SiteSpec getBlank() {
|
||||||
|
new SiteSpec('', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
static SiteSpec concat(SiteSpec s0, SiteSpec s1) {
|
||||||
|
new SiteSpec(
|
||||||
|
s1.name.blank ? s0.name : s1.name,
|
||||||
|
s1.baseUrl.blank ? s0.baseUrl : s1.baseUrl
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
final String name
|
||||||
|
final String baseUrl
|
||||||
|
|
||||||
|
SiteSpec plus(SiteSpec other) {
|
||||||
|
concat(this, other)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"SiteSpec(${ this.name }, ${ this.baseUrl })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.SiteSpec
|
||||||
|
import com.jessebrault.ssg.task.TaskFactorySpec
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
import java.util.function.Function
|
||||||
|
|
||||||
|
@TupleConstructor(defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class Build {
|
||||||
|
|
||||||
|
@TupleConstructor(defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode
|
||||||
|
static final class AllBuilds {
|
||||||
|
|
||||||
|
static AllBuilds concat(AllBuilds ab0, AllBuilds ab1) {
|
||||||
|
new AllBuilds(
|
||||||
|
ab0.siteSpec + ab1.siteSpec,
|
||||||
|
ab0.globals + ab1.globals,
|
||||||
|
ab0.taskFactorySpecs + ab1.taskFactorySpecs
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
static AllBuilds getEmpty() {
|
||||||
|
new AllBuilds(SiteSpec.getBlank(), [:], [])
|
||||||
|
}
|
||||||
|
|
||||||
|
final SiteSpec siteSpec
|
||||||
|
final Map<String, Object> globals
|
||||||
|
final Collection<TaskFactorySpec> taskFactorySpecs
|
||||||
|
|
||||||
|
AllBuilds plus(AllBuilds other) {
|
||||||
|
concat(this, other)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static Build getEmpty() {
|
||||||
|
new Build(
|
||||||
|
'',
|
||||||
|
OutputDirFunctions.DEFAULT,
|
||||||
|
SiteSpec.getBlank(),
|
||||||
|
[:],
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
static Build get(Map<String, Object> args) {
|
||||||
|
new Build(
|
||||||
|
args?.name as String ?: '',
|
||||||
|
args?.outputDirFunction as Function<Build, OutputDir> ?: OutputDirFunctions.DEFAULT,
|
||||||
|
args?.siteSpec as SiteSpec ?: SiteSpec.getBlank(),
|
||||||
|
args?.globals as Map<String, Object> ?: [:],
|
||||||
|
args?.taskFactorySpecs as Collection<TaskFactorySpec> ?: []
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
static Build concat(Build b0, Build b1) {
|
||||||
|
new Build(
|
||||||
|
b1.name.blank ? b0.name : b1.name,
|
||||||
|
OutputDirFunctions.concat(b0.outputDirFunction, b1.outputDirFunction),
|
||||||
|
SiteSpec.concat(b0.siteSpec, b1.siteSpec),
|
||||||
|
b0.globals + b1.globals,
|
||||||
|
b0.taskFactorySpecs + b1.taskFactorySpecs
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
static Build from(AllBuilds allBuilds) {
|
||||||
|
new Build(
|
||||||
|
'',
|
||||||
|
OutputDirFunctions.DEFAULT,
|
||||||
|
allBuilds.siteSpec,
|
||||||
|
allBuilds.globals,
|
||||||
|
allBuilds.taskFactorySpecs
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
final String name
|
||||||
|
final Function<Build, OutputDir> outputDirFunction
|
||||||
|
final SiteSpec siteSpec
|
||||||
|
final Map<String, Object> globals
|
||||||
|
final Collection<TaskFactorySpec> taskFactorySpecs
|
||||||
|
|
||||||
|
Build plus(Build other) {
|
||||||
|
concat(this, other)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript
|
||||||
|
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.buildscript.Build.AllBuilds
|
||||||
|
import com.jessebrault.ssg.buildscript.dsl.AllBuildsDelegate
|
||||||
|
import com.jessebrault.ssg.buildscript.dsl.BuildDelegate
|
||||||
|
|
||||||
|
abstract class BuildScriptBase extends Script {
|
||||||
|
|
||||||
|
private final Collection<AllBuildsDelegate> allBuildsDelegates = []
|
||||||
|
private final Collection<BuildDelegate> buildDelegates = []
|
||||||
|
|
||||||
|
private int currentBuildNumber = 0
|
||||||
|
|
||||||
|
final AllBuilds defaultAllBuilds = AllBuilds.getEmpty()
|
||||||
|
|
||||||
|
void build(
|
||||||
|
@DelegatesTo(value = BuildDelegate, strategy = Closure.DELEGATE_FIRST)
|
||||||
|
Closure<Void> buildClosure
|
||||||
|
) {
|
||||||
|
this.build('build' + this.currentBuildNumber, buildClosure)
|
||||||
|
}
|
||||||
|
|
||||||
|
void build(
|
||||||
|
String name,
|
||||||
|
@DelegatesTo(value = BuildDelegate, strategy = Closure.DELEGATE_FIRST)
|
||||||
|
Closure<Void> buildClosure
|
||||||
|
) {
|
||||||
|
def d = new BuildDelegate().tap {
|
||||||
|
it.name = name
|
||||||
|
}
|
||||||
|
buildClosure.setDelegate(d)
|
||||||
|
buildClosure.setResolveStrategy(Closure.DELEGATE_FIRST)
|
||||||
|
buildClosure()
|
||||||
|
this.buildDelegates << d
|
||||||
|
this.currentBuildNumber++
|
||||||
|
}
|
||||||
|
|
||||||
|
void allBuilds(
|
||||||
|
@DelegatesTo(value = AllBuildsDelegate, strategy = Closure.DELEGATE_FIRST)
|
||||||
|
Closure<Void> allBuildsClosure
|
||||||
|
) {
|
||||||
|
def d = new AllBuildsDelegate()
|
||||||
|
allBuildsClosure.setDelegate(d)
|
||||||
|
allBuildsClosure.setResolveStrategy(Closure.DELEGATE_FIRST)
|
||||||
|
allBuildsClosure()
|
||||||
|
this.allBuildsDelegates << d
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection<Build> getBuilds() {
|
||||||
|
def allBuilds = this.defaultAllBuilds
|
||||||
|
this.allBuildsDelegates.each {
|
||||||
|
allBuilds += it.getResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
def baseBuild = Build.from(allBuilds)
|
||||||
|
this.buildDelegates.collect {
|
||||||
|
baseBuild + it.getResult()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript
|
||||||
|
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
interface BuildScriptConfiguratorFactory {
|
||||||
|
Consumer<BuildScriptBase> get()
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript
|
||||||
|
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import org.codehaus.groovy.control.CompilerConfiguration
|
||||||
|
import org.codehaus.groovy.control.customizers.ImportCustomizer
|
||||||
|
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
@NullCheck
|
||||||
|
final class BuildScriptUtil {
|
||||||
|
|
||||||
|
// TODO: check exactly what we are importing to the script automatically
|
||||||
|
// TODO: check the roots arg, do we include 'ssgBuilds'/'buildSrc' dir eventually?
|
||||||
|
static Collection<Build> runBuildScript(String relativePath, Consumer<BuildScriptBase> configureBuildScript) {
|
||||||
|
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.page',
|
||||||
|
'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
|
||||||
|
configureBuildScript.accept(buildScript)
|
||||||
|
buildScript()
|
||||||
|
buildScript.getBuilds()
|
||||||
|
}
|
||||||
|
|
||||||
|
private BuildScriptUtil() {}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,97 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.html.PageToHtmlTaskFactory
|
||||||
|
import com.jessebrault.ssg.html.TextToHtmlSpec
|
||||||
|
import com.jessebrault.ssg.html.TextToHtmlTaskFactory
|
||||||
|
import com.jessebrault.ssg.page.PageTypes
|
||||||
|
import com.jessebrault.ssg.page.PagesProviders
|
||||||
|
import com.jessebrault.ssg.part.Part
|
||||||
|
import com.jessebrault.ssg.part.PartTypes
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProviders
|
||||||
|
import com.jessebrault.ssg.template.TemplatesProviders
|
||||||
|
import com.jessebrault.ssg.text.TextsProviders
|
||||||
|
import com.jessebrault.ssg.template.Template
|
||||||
|
import com.jessebrault.ssg.template.TemplateTypes
|
||||||
|
import com.jessebrault.ssg.text.TextTypes
|
||||||
|
import com.jessebrault.ssg.util.ExtensionUtil
|
||||||
|
import com.jessebrault.ssg.util.PathUtil
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
final class DefaultBuildScriptConfiguratorFactory implements BuildScriptConfiguratorFactory {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(DefaultBuildScriptConfiguratorFactory)
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Consumer<BuildScriptBase> get() {
|
||||||
|
return {
|
||||||
|
it.allBuilds {
|
||||||
|
types {
|
||||||
|
textTypes << TextTypes.MARKDOWN
|
||||||
|
pageTypes << PageTypes.GSP
|
||||||
|
templateTypes << TemplateTypes.GSP
|
||||||
|
partTypes << PartTypes.GSP
|
||||||
|
|
||||||
|
//noinspection GroovyUnnecessaryReturn
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
providers { types ->
|
||||||
|
texts(TextsProviders.from(new File('texts'), types.textTypes))
|
||||||
|
pages(PagesProviders.from(new File('pages'), types.pageTypes))
|
||||||
|
templates(TemplatesProviders.from(new File('templates'), types.templateTypes))
|
||||||
|
|
||||||
|
parts(CollectionProviders.from(new File('parts')) { File file ->
|
||||||
|
def extension = ExtensionUtil.getExtension(file.path)
|
||||||
|
def partType = types.partTypes.find { it.ids.contains(extension) }
|
||||||
|
if (!partType) {
|
||||||
|
logger.warn('there is no PartType for file {}; skipping', file)
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
new Part(PathUtil.relative('parts', file.path), partType, file.getText())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
taskFactories { sp ->
|
||||||
|
register('textToHtml', TextToHtmlTaskFactory::new) {
|
||||||
|
it.specProvider += CollectionProviders.from {
|
||||||
|
def templates = sp.templatesProvider.provide()
|
||||||
|
sp.textsProvider.provide().collect {
|
||||||
|
def frontMatterResult = it.type.frontMatterGetter.get(it)
|
||||||
|
if (frontMatterResult.hasDiagnostics()) {
|
||||||
|
return Result.ofDiagnostics(frontMatterResult.diagnostics)
|
||||||
|
}
|
||||||
|
def templateValue = frontMatterResult.get().get('template')
|
||||||
|
if (templateValue) {
|
||||||
|
def template = templates.find { it.path == templateValue }
|
||||||
|
return Result.of(new TextToHtmlSpec(it, template, it.path))
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
it.allTextsProvider += sp.textsProvider
|
||||||
|
it.allPartsProvider += sp.partsProvider
|
||||||
|
|
||||||
|
//noinspection GroovyUnnecessaryReturn
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
register('pageToHtml', PageToHtmlTaskFactory::new) {
|
||||||
|
it.pagesProvider += sp.pagesProvider
|
||||||
|
it.allTextsProvider += sp.textsProvider
|
||||||
|
it.allPartsProvider += sp.partsProvider
|
||||||
|
|
||||||
|
//noinspection GroovyUnnecessaryReturn
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,32 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript
|
||||||
|
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import org.jetbrains.annotations.Nullable
|
||||||
|
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class OutputDir {
|
||||||
|
|
||||||
|
static OutputDir concat(OutputDir od0, OutputDir od1) {
|
||||||
|
new OutputDir(od1.path ? od1.path : od0.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
final String path
|
||||||
|
|
||||||
|
OutputDir(@Nullable String path) {
|
||||||
|
this.path = path
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputDir(File file) {
|
||||||
|
this.path = file.path
|
||||||
|
}
|
||||||
|
|
||||||
|
File getFile() {
|
||||||
|
this.path ? new File(this.path) : new File('')
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputDir plus(OutputDir other) {
|
||||||
|
concat(this, other)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript
|
||||||
|
|
||||||
|
import groovy.transform.stc.ClosureParams
|
||||||
|
import groovy.transform.stc.SimpleType
|
||||||
|
|
||||||
|
import java.util.function.Function
|
||||||
|
|
||||||
|
final class OutputDirFunctions {
|
||||||
|
|
||||||
|
static final Function<Build, OutputDir> DEFAULT = of { new OutputDir(it.name) }
|
||||||
|
|
||||||
|
static Function<Build, OutputDir> concat(
|
||||||
|
Function<Build, OutputDir> f0,
|
||||||
|
Function<Build, OutputDir> f1
|
||||||
|
) {
|
||||||
|
f1 == OutputDirFunctions.DEFAULT ? f0 : f1
|
||||||
|
}
|
||||||
|
|
||||||
|
static Function<Build, OutputDir> of(
|
||||||
|
@ClosureParams(value = SimpleType, options = 'com.jessebrault.ssg.buildscript.Build')
|
||||||
|
Closure<OutputDir> closure
|
||||||
|
) {
|
||||||
|
closure as Function<Build, OutputDir>
|
||||||
|
}
|
||||||
|
|
||||||
|
static Function<Build, OutputDir> of(File dir) {
|
||||||
|
of { new OutputDir(dir) }
|
||||||
|
}
|
||||||
|
|
||||||
|
static Function<Build, OutputDir> of(String path) {
|
||||||
|
of { new OutputDir(path) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private OutputDirFunctions() {}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.model.Model
|
||||||
|
import com.jessebrault.ssg.page.Page
|
||||||
|
import com.jessebrault.ssg.part.Part
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProvider
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProviders
|
||||||
|
import com.jessebrault.ssg.template.Template
|
||||||
|
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 SourceProviders {
|
||||||
|
|
||||||
|
static SourceProviders concat(SourceProviders sp0, SourceProviders sp1) {
|
||||||
|
new SourceProviders(
|
||||||
|
sp0.textsProvider + sp1.textsProvider,
|
||||||
|
sp0.modelsProvider + sp1.modelsProvider,
|
||||||
|
sp0.pagesProvider + sp1.pagesProvider,
|
||||||
|
sp0.templatesProvider + sp1.templatesProvider,
|
||||||
|
sp0.partsProvider + sp1.partsProvider
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
static SourceProviders get(Map<String, Object> args) {
|
||||||
|
new SourceProviders(
|
||||||
|
args?.textsProvider as CollectionProvider<Text>
|
||||||
|
?: CollectionProviders.getEmpty() as CollectionProvider<Text>,
|
||||||
|
args?.modelsProvider as CollectionProvider<Model<Object>>
|
||||||
|
?: CollectionProviders.getEmpty() as CollectionProvider<Model<Object>>,
|
||||||
|
args?.pagesProvider as CollectionProvider<Page>
|
||||||
|
?: CollectionProviders.getEmpty() as CollectionProvider<Page>,
|
||||||
|
args?.templatesProvider as CollectionProvider<Template>
|
||||||
|
?: CollectionProviders.getEmpty() as CollectionProvider<Template>,
|
||||||
|
args?.partsProvider as CollectionProvider<Part>
|
||||||
|
?: CollectionProviders.getEmpty() as CollectionProvider<Part>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
static SourceProviders getEmpty() {
|
||||||
|
new SourceProviders(
|
||||||
|
CollectionProviders.getEmpty(),
|
||||||
|
CollectionProviders.getEmpty(),
|
||||||
|
CollectionProviders.getEmpty(),
|
||||||
|
CollectionProviders.getEmpty(),
|
||||||
|
CollectionProviders.getEmpty()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
final CollectionProvider<Text> textsProvider
|
||||||
|
final CollectionProvider<Model<Object>> modelsProvider
|
||||||
|
final CollectionProvider<Page> pagesProvider
|
||||||
|
final CollectionProvider<Template> templatesProvider
|
||||||
|
final CollectionProvider<Part> partsProvider
|
||||||
|
|
||||||
|
SourceProviders plus(SourceProviders other) {
|
||||||
|
concat(this, other)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.page.PageType
|
||||||
|
import com.jessebrault.ssg.part.PartType
|
||||||
|
import com.jessebrault.ssg.template.TemplateType
|
||||||
|
import com.jessebrault.ssg.text.TextType
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@TupleConstructor(defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class TypesContainer {
|
||||||
|
|
||||||
|
static TypesContainer getEmpty() {
|
||||||
|
new TypesContainer([], [], [], [])
|
||||||
|
}
|
||||||
|
|
||||||
|
static TypesContainer concat(TypesContainer tc0, TypesContainer tc1) {
|
||||||
|
new TypesContainer(
|
||||||
|
tc0.textTypes + tc1.textTypes,
|
||||||
|
tc0.pageTypes + tc1.pageTypes,
|
||||||
|
tc0.templateTypes + tc1.templateTypes,
|
||||||
|
tc0.partTypes + tc1.partTypes
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection<TextType> textTypes
|
||||||
|
Collection<PageType> pageTypes
|
||||||
|
Collection<TemplateType> templateTypes
|
||||||
|
Collection<PartType> partTypes
|
||||||
|
|
||||||
|
TypesContainer plus(TypesContainer other) {
|
||||||
|
concat(this, other)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,111 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript.dsl
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.SiteSpec
|
||||||
|
import com.jessebrault.ssg.buildscript.SourceProviders
|
||||||
|
import com.jessebrault.ssg.buildscript.TypesContainer
|
||||||
|
import com.jessebrault.ssg.task.TaskFactorySpec
|
||||||
|
import groovy.transform.stc.ClosureParams
|
||||||
|
import groovy.transform.stc.SimpleType
|
||||||
|
|
||||||
|
abstract class AbstractBuildDelegate<T> {
|
||||||
|
|
||||||
|
private final Collection<Closure<Void>> siteSpecClosures = []
|
||||||
|
private final Collection<Closure<Void>> globalsClosures = []
|
||||||
|
private final Collection<Closure<Void>> typesClosures = []
|
||||||
|
private final Collection<Closure<Void>> sourcesClosures = []
|
||||||
|
private final Collection<Closure<Void>> taskFactoriesClosures = []
|
||||||
|
|
||||||
|
abstract T getResult()
|
||||||
|
|
||||||
|
protected final SiteSpec getSiteSpecResult() {
|
||||||
|
this.siteSpecClosures.inject(SiteSpec.getBlank()) { acc, closure ->
|
||||||
|
def d = new SiteSpecDelegate()
|
||||||
|
closure.delegate = d
|
||||||
|
closure.resolveStrategy = DELEGATE_FIRST
|
||||||
|
closure()
|
||||||
|
acc + d.getResult()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final Map<String, Object> getGlobalsResult() {
|
||||||
|
this.globalsClosures.inject([:] as Map<String, Object>) { acc, closure ->
|
||||||
|
def d = new GlobalsDelegate()
|
||||||
|
closure.delegate = d
|
||||||
|
closure.resolveStrategy = DELEGATE_FIRST
|
||||||
|
closure()
|
||||||
|
acc + d.getResult()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final TypesContainer getTypesResult() {
|
||||||
|
this.typesClosures.inject(TypesContainer.getEmpty()) { acc, closure ->
|
||||||
|
def d = new TypesDelegate()
|
||||||
|
closure.delegate = d
|
||||||
|
closure.resolveStrategy = DELEGATE_FIRST
|
||||||
|
closure()
|
||||||
|
acc + d.getResult()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final SourceProviders getSourcesResult(TypesContainer typesContainer) {
|
||||||
|
this.sourcesClosures.inject(SourceProviders.getEmpty()) { acc, closure ->
|
||||||
|
def d = new SourceProvidersDelegate()
|
||||||
|
closure.delegate = d
|
||||||
|
closure.resolveStrategy = DELEGATE_FIRST
|
||||||
|
closure(typesContainer)
|
||||||
|
acc + d.getResult()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final Collection<TaskFactorySpec> getTaskFactoriesResult(SourceProviders sourceProviders) {
|
||||||
|
this.taskFactoriesClosures.inject([:] as Map<String, TaskFactorySpec>) { acc, closure ->
|
||||||
|
def d = new TaskFactoriesDelegate()
|
||||||
|
closure.delegate = d
|
||||||
|
closure.resolveStrategy = DELEGATE_FIRST
|
||||||
|
closure(sourceProviders)
|
||||||
|
def specs = d.getResult()
|
||||||
|
specs.forEach { name, spec ->
|
||||||
|
acc.merge(name, spec) { spec0, spec1 -> spec0 + spec1 }
|
||||||
|
}
|
||||||
|
acc
|
||||||
|
}.values()
|
||||||
|
}
|
||||||
|
|
||||||
|
void siteSpec(
|
||||||
|
@DelegatesTo(value = SiteSpecDelegate, strategy = Closure.DELEGATE_FIRST)
|
||||||
|
Closure<Void> siteSpecClosure
|
||||||
|
) {
|
||||||
|
this.siteSpecClosures << siteSpecClosure
|
||||||
|
}
|
||||||
|
|
||||||
|
void globals(
|
||||||
|
@DelegatesTo(value = GlobalsDelegate, strategy = Closure.DELEGATE_FIRST)
|
||||||
|
Closure<Void> globalsClosure
|
||||||
|
) {
|
||||||
|
this.globalsClosures << globalsClosure
|
||||||
|
}
|
||||||
|
|
||||||
|
void types(
|
||||||
|
@DelegatesTo(value = TypesDelegate, strategy = Closure.DELEGATE_FIRST)
|
||||||
|
Closure<Void> typesClosure
|
||||||
|
) {
|
||||||
|
this.typesClosures << typesClosure
|
||||||
|
}
|
||||||
|
|
||||||
|
void providers(
|
||||||
|
@DelegatesTo(value = SourceProvidersDelegate, strategy = Closure.DELEGATE_FIRST)
|
||||||
|
@ClosureParams(value = SimpleType, options = 'com.jessebrault.ssg.buildscript.TypesContainer')
|
||||||
|
Closure<Void> providersClosure
|
||||||
|
) {
|
||||||
|
this.sourcesClosures << providersClosure
|
||||||
|
}
|
||||||
|
|
||||||
|
void taskFactories(
|
||||||
|
@DelegatesTo(value = TaskFactoriesDelegate, strategy = Closure.DELEGATE_FIRST)
|
||||||
|
@ClosureParams(value = SimpleType, options = 'com.jessebrault.ssg.buildscript.SourceProviders')
|
||||||
|
Closure<Void> taskFactoriesClosure
|
||||||
|
) {
|
||||||
|
this.taskFactoriesClosures << taskFactoriesClosure
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript.dsl
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.buildscript.Build
|
||||||
|
|
||||||
|
final class AllBuildsDelegate extends AbstractBuildDelegate<Build.AllBuilds> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Build.AllBuilds getResult() {
|
||||||
|
new Build.AllBuilds(
|
||||||
|
this.getSiteSpecResult(),
|
||||||
|
this.getGlobalsResult(),
|
||||||
|
this.getTaskFactoriesResult(this.getSourcesResult(this.getTypesResult()))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript.dsl
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.buildscript.Build
|
||||||
|
import com.jessebrault.ssg.buildscript.OutputDir
|
||||||
|
import com.jessebrault.ssg.buildscript.OutputDirFunctions
|
||||||
|
import org.jetbrains.annotations.Nullable
|
||||||
|
|
||||||
|
import java.util.function.Function
|
||||||
|
|
||||||
|
final class BuildDelegate extends AbstractBuildDelegate<Build> {
|
||||||
|
|
||||||
|
String name = ''
|
||||||
|
private Function<Build, OutputDir> outputDirFunction = OutputDirFunctions.DEFAULT
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Build getResult() {
|
||||||
|
new Build(
|
||||||
|
this.name,
|
||||||
|
this.outputDirFunction,
|
||||||
|
this.getSiteSpecResult(),
|
||||||
|
this.getGlobalsResult(),
|
||||||
|
this.getTaskFactoriesResult(this.getSourcesResult(this.getTypesResult()))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
void setOutputDirFunction(Function<Build, OutputDir> outputDirFunction) {
|
||||||
|
this.outputDirFunction = outputDirFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
void setOutputDir(File file) {
|
||||||
|
this.outputDirFunction = { new OutputDir(file) }
|
||||||
|
}
|
||||||
|
|
||||||
|
void setOutputDir(@Nullable String path) {
|
||||||
|
this.outputDirFunction = { new OutputDir(path) }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript.dsl
|
||||||
|
|
||||||
|
final class GlobalsDelegate {
|
||||||
|
|
||||||
|
@Delegate
|
||||||
|
final Map<String, Object> globals = [:]
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Object getProperty(String propertyName) {
|
||||||
|
this.globals[propertyName]
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void setProperty(String propertyName, Object newValue) {
|
||||||
|
this.globals[propertyName] = newValue
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object> getResult() {
|
||||||
|
this.globals
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript.dsl
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.SiteSpec
|
||||||
|
|
||||||
|
final class SiteSpecDelegate {
|
||||||
|
|
||||||
|
String name
|
||||||
|
String baseUrl
|
||||||
|
|
||||||
|
SiteSpecDelegate() {
|
||||||
|
def blank = SiteSpec.getBlank()
|
||||||
|
this.name = blank.name
|
||||||
|
this.baseUrl = blank.baseUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
SiteSpec getResult() {
|
||||||
|
new SiteSpec(this.name, this.baseUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript.dsl
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.buildscript.SourceProviders
|
||||||
|
import com.jessebrault.ssg.model.Model
|
||||||
|
import com.jessebrault.ssg.page.Page
|
||||||
|
import com.jessebrault.ssg.part.Part
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProvider
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProviders
|
||||||
|
import com.jessebrault.ssg.template.Template
|
||||||
|
import com.jessebrault.ssg.text.Text
|
||||||
|
|
||||||
|
final class SourceProvidersDelegate {
|
||||||
|
|
||||||
|
private CollectionProvider<Text> textsProvider = CollectionProviders.getEmpty()
|
||||||
|
private CollectionProvider<Model<Object>> modelsProvider = CollectionProviders.getEmpty()
|
||||||
|
private CollectionProvider<Page> pagesProvider = CollectionProviders.getEmpty()
|
||||||
|
private CollectionProvider<Template> templatesProvider = CollectionProviders.getEmpty()
|
||||||
|
private CollectionProvider<Part> partsProvider = CollectionProviders.getEmpty()
|
||||||
|
|
||||||
|
void texts(CollectionProvider<Text> textsProvider) {
|
||||||
|
this.textsProvider += textsProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
void models(CollectionProvider<Model<?>> modelsProvider) {
|
||||||
|
this.modelsProvider += modelsProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
void pages(CollectionProvider<Page> pagesProvider) {
|
||||||
|
this.pagesProvider += pagesProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
void templates(CollectionProvider<Template> templatesProvider) {
|
||||||
|
this.templatesProvider += templatesProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
void parts(CollectionProvider<Part> partsProvider) {
|
||||||
|
this.partsProvider += partsProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
SourceProviders getResult() {
|
||||||
|
new SourceProviders(
|
||||||
|
this.textsProvider,
|
||||||
|
this.modelsProvider,
|
||||||
|
this.pagesProvider,
|
||||||
|
this.templatesProvider,
|
||||||
|
this.partsProvider
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript.dsl
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.task.TaskFactory
|
||||||
|
import com.jessebrault.ssg.task.TaskFactorySpec
|
||||||
|
import groovy.transform.stc.ClosureParams
|
||||||
|
import groovy.transform.stc.SecondParam
|
||||||
|
|
||||||
|
import java.util.function.Supplier
|
||||||
|
|
||||||
|
final class TaskFactoriesDelegate {
|
||||||
|
|
||||||
|
private final Map<String, TaskFactorySpec> specs = [:]
|
||||||
|
|
||||||
|
private void checkNotRegistered(String name) {
|
||||||
|
if (this.specs.containsKey(name)) {
|
||||||
|
throw new IllegalArgumentException("a TaskFactory is already registered by the name ${ name }")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void register(String name, Supplier<? extends TaskFactory> factorySupplier) {
|
||||||
|
this.checkNotRegistered(name)
|
||||||
|
this.specs[name] = new TaskFactorySpec(factorySupplier, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
def <T extends TaskFactory> void register(
|
||||||
|
String name,
|
||||||
|
Supplier<T> factorySupplier,
|
||||||
|
@ClosureParams(value = SecondParam.FirstGenericType)
|
||||||
|
Closure<Void> factoryConfigureClosure
|
||||||
|
) {
|
||||||
|
this.checkNotRegistered(name)
|
||||||
|
this.specs[name] = new TaskFactorySpec(factorySupplier, [factoryConfigureClosure])
|
||||||
|
}
|
||||||
|
|
||||||
|
void configure(String name, Closure<Void> factoryConfigureClosure) {
|
||||||
|
if (!this.specs.containsKey(name)) {
|
||||||
|
throw new IllegalArgumentException("there is no TaskFactory registered by name ${ name }")
|
||||||
|
}
|
||||||
|
this.specs[name].configureClosures << factoryConfigureClosure
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, TaskFactorySpec> getResult() {
|
||||||
|
this.specs
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript.dsl
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.buildscript.TypesContainer
|
||||||
|
import com.jessebrault.ssg.page.PageType
|
||||||
|
import com.jessebrault.ssg.part.PartType
|
||||||
|
import com.jessebrault.ssg.template.TemplateType
|
||||||
|
import com.jessebrault.ssg.text.TextType
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
|
||||||
|
@NullCheck
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class TypesDelegate {
|
||||||
|
|
||||||
|
final Collection<TextType> textTypes = []
|
||||||
|
final Collection<PageType> pageTypes = []
|
||||||
|
final Collection<TemplateType> templateTypes = []
|
||||||
|
final Collection<PartType> partTypes = []
|
||||||
|
|
||||||
|
TypesContainer getResult() {
|
||||||
|
new TypesContainer(this.textTypes, this.pageTypes, this.templateTypes, this.partTypes)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
package com.jessebrault.ssg.dsl
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.part.Part
|
||||||
|
import com.jessebrault.ssg.render.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)
|
||||||
|
final class EmbeddablePart {
|
||||||
|
|
||||||
|
private final Part part
|
||||||
|
private final RenderContext context
|
||||||
|
private final Closure<Void> onDiagnostics
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final Text text
|
||||||
|
|
||||||
|
EmbeddablePart(
|
||||||
|
Part part,
|
||||||
|
RenderContext context,
|
||||||
|
Closure<Void> 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.hasDiagnostics()) {
|
||||||
|
this.onDiagnostics.call(result.diagnostics)
|
||||||
|
''
|
||||||
|
} else {
|
||||||
|
result.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"EmbeddablePart(part: ${ this.part })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.jessebrault.ssg.dsl
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.render.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)
|
||||||
|
final class EmbeddablePartsMap {
|
||||||
|
|
||||||
|
@Delegate
|
||||||
|
private final Map<String, EmbeddablePart> partsMap = [:]
|
||||||
|
|
||||||
|
EmbeddablePartsMap(
|
||||||
|
RenderContext context,
|
||||||
|
Closure<Void> onDiagnostics,
|
||||||
|
@Nullable Text text = null
|
||||||
|
) {
|
||||||
|
requireNonNull(context)
|
||||||
|
requireNonNull(onDiagnostics)
|
||||||
|
context.parts.each {
|
||||||
|
this.put(it.path, new EmbeddablePart(it, context, onDiagnostics, text))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"EmbeddablePartsMap(partsMap: ${ this.partsMap })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
package com.jessebrault.ssg.dsl
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.text.FrontMatter
|
||||||
|
import com.jessebrault.ssg.text.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)
|
||||||
|
final class EmbeddableText {
|
||||||
|
|
||||||
|
private final Text text
|
||||||
|
private final Closure<Void> onDiagnostics
|
||||||
|
|
||||||
|
@Memoized
|
||||||
|
String render() {
|
||||||
|
def result = this.text.type.renderer.render(this.text)
|
||||||
|
if (result.diagnostics.size() > 0) {
|
||||||
|
this.onDiagnostics.call(result.diagnostics)
|
||||||
|
''
|
||||||
|
} else {
|
||||||
|
result.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Memoized
|
||||||
|
FrontMatter getFrontMatter() {
|
||||||
|
def result = this.text.type.frontMatterGetter.get(this.text)
|
||||||
|
if (result.hasDiagnostics()) {
|
||||||
|
this.onDiagnostics.call(result.diagnostics)
|
||||||
|
new FrontMatter(this.text, [:])
|
||||||
|
} else {
|
||||||
|
result.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Memoized
|
||||||
|
String getExcerpt(int limit) {
|
||||||
|
def result = this.text.type.excerptGetter.getExcerpt(this.text, limit)
|
||||||
|
if (result.hasDiagnostics()) {
|
||||||
|
this.onDiagnostics.call(result.diagnostics)
|
||||||
|
''
|
||||||
|
} else {
|
||||||
|
result.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getPath() {
|
||||||
|
this.text.path
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"EmbeddableText(text: ${ this.text })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.jessebrault.ssg.dsl
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.text.Text
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
|
||||||
|
@NullCheck
|
||||||
|
@EqualsAndHashCode(includeFields = true)
|
||||||
|
final class EmbeddableTextsCollection {
|
||||||
|
|
||||||
|
@Delegate
|
||||||
|
private final Collection<EmbeddableText> embeddableTexts = []
|
||||||
|
|
||||||
|
EmbeddableTextsCollection(Collection<Text> texts, Closure<Void> onDiagnostics) {
|
||||||
|
texts.each {
|
||||||
|
this << new EmbeddableText(it, onDiagnostics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"EmbeddableTextsCollection(embeddableTexts: ${ this.embeddableTexts })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package com.jessebrault.ssg.dsl
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.model.Model
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import org.jetbrains.annotations.Nullable
|
||||||
|
|
||||||
|
@NullCheck
|
||||||
|
@EqualsAndHashCode(includeFields = true)
|
||||||
|
final class ModelCollection<T> {
|
||||||
|
|
||||||
|
@Delegate
|
||||||
|
private final Collection<Model<T>> ts = []
|
||||||
|
|
||||||
|
ModelCollection(Collection<Model<T>> ts) {
|
||||||
|
this.ts.addAll(ts)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
Model<T> getByName(String name) {
|
||||||
|
this.ts.find { it.name == name }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
<E extends T> Model<E> getByNameAndType(String name, Class<E> type) {
|
||||||
|
this.ts.find { it.name == name && type.isAssignableFrom(it.get().class) } as Model<E>
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<Model<T>> findByName(String name) {
|
||||||
|
Optional.ofNullable(this.getByName(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
def <E extends T> Optional<Model<E>> findByNameAndType(String name, Class<E> type) {
|
||||||
|
Optional.ofNullable(this.getByNameAndType(name, type))
|
||||||
|
}
|
||||||
|
|
||||||
|
def <E extends T> ModelCollection<E> findAllByType(Class<E> type) {
|
||||||
|
def es = this.ts.findResults {
|
||||||
|
type.isAssignableFrom(it.get().class) ? it as Model<E> : null
|
||||||
|
}
|
||||||
|
new ModelCollection<>(es)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
package com.jessebrault.ssg.dsl
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.dsl.tagbuilder.DynamicTagBuilder
|
||||||
|
import com.jessebrault.ssg.dsl.urlbuilder.PathBasedUrlBuilder
|
||||||
|
import com.jessebrault.ssg.render.RenderContext
|
||||||
|
import com.jessebrault.ssg.text.Text
|
||||||
|
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<String, Object> custom = [:]
|
||||||
|
|
||||||
|
String loggerName = ''
|
||||||
|
Closure<Void> onDiagnostics = { }
|
||||||
|
Text text = null
|
||||||
|
|
||||||
|
void putCustom(String key, Object value) {
|
||||||
|
this.custom.put(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
void putAllCustom(Map<String, Object> m) {
|
||||||
|
this.custom.putAll(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, Object> get(
|
||||||
|
RenderContext context,
|
||||||
|
@DelegatesTo(value = Builder, strategy = Closure.DELEGATE_FIRST)
|
||||||
|
@ClosureParams(
|
||||||
|
value = SimpleType,
|
||||||
|
options = ['com.jessebrault.ssg.dsl.StandardDslMap.Builder']
|
||||||
|
)
|
||||||
|
Closure<Void> builderClosure
|
||||||
|
) {
|
||||||
|
def b = new Builder()
|
||||||
|
builderClosure.resolveStrategy = Closure.DELEGATE_FIRST
|
||||||
|
builderClosure.delegate = b
|
||||||
|
builderClosure(b)
|
||||||
|
|
||||||
|
[:].tap {
|
||||||
|
// standard variables
|
||||||
|
it.globals = context.globals
|
||||||
|
it.logger = LoggerFactory.getLogger(b.loggerName)
|
||||||
|
it.models = new ModelCollection<Object>(context.models)
|
||||||
|
it.parts = new EmbeddablePartsMap(
|
||||||
|
context,
|
||||||
|
b.onDiagnostics,
|
||||||
|
b.text
|
||||||
|
)
|
||||||
|
it.siteSpec = context.siteSpec
|
||||||
|
it.sourcePath = context.sourcePath
|
||||||
|
it.tagBuilder = new DynamicTagBuilder()
|
||||||
|
it.targetPath = context.targetPath
|
||||||
|
it.tasks = new TaskCollection(context.tasks)
|
||||||
|
it.text = b.text ? new EmbeddableText(
|
||||||
|
b.text,
|
||||||
|
b.onDiagnostics
|
||||||
|
) : null
|
||||||
|
it.texts = new EmbeddableTextsCollection(
|
||||||
|
context.texts,
|
||||||
|
b.onDiagnostics
|
||||||
|
)
|
||||||
|
it.urlBuilder = new PathBasedUrlBuilder(
|
||||||
|
context.targetPath,
|
||||||
|
context.siteSpec.baseUrl
|
||||||
|
)
|
||||||
|
|
||||||
|
// task types
|
||||||
|
it.Task = com.jessebrault.ssg.task.Task
|
||||||
|
it.HtmlTask = com.jessebrault.ssg.html.HtmlTask
|
||||||
|
it.ModelToHtmlTask = com.jessebrault.ssg.html.ModelToHtmlTask
|
||||||
|
it.PageToHtmlTask = com.jessebrault.ssg.html.PageToHtmlTask
|
||||||
|
it.TextToHtmlTask = com.jessebrault.ssg.html.TextToHtmlTask
|
||||||
|
|
||||||
|
// custom
|
||||||
|
it.putAll(b.custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package com.jessebrault.ssg.dsl
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.task.Task
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
|
||||||
|
@EqualsAndHashCode(includeFields = true)
|
||||||
|
final class TaskCollection {
|
||||||
|
|
||||||
|
@Delegate
|
||||||
|
private final Collection<Task> tasks
|
||||||
|
|
||||||
|
TaskCollection(Collection<? extends Task> src = []) {
|
||||||
|
this.tasks = []
|
||||||
|
this.tasks.addAll(src)
|
||||||
|
}
|
||||||
|
|
||||||
|
def <T extends Task> Collection<T> byType(Class<T> taskClass) {
|
||||||
|
this.tasks.findAll { taskClass.isAssignableFrom(it.class) } as Collection<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,77 @@
|
|||||||
|
package com.jessebrault.ssg.dsl.tagbuilder
|
||||||
|
|
||||||
|
import org.codehaus.groovy.runtime.InvokerHelper
|
||||||
|
|
||||||
|
final 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package com.jessebrault.ssg.dsl.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)
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package com.jessebrault.ssg.dsl.urlbuilder
|
||||||
|
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
final 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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.jessebrault.ssg.dsl.urlbuilder
|
||||||
|
|
||||||
|
interface UrlBuilder {
|
||||||
|
String getAbsolute()
|
||||||
|
String absolute(String to)
|
||||||
|
String relative(String to)
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package com.jessebrault.ssg.html
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.task.AbstractTask
|
||||||
|
import com.jessebrault.ssg.task.Task
|
||||||
|
import com.jessebrault.ssg.util.Diagnostic
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
|
||||||
|
@NullCheck
|
||||||
|
@EqualsAndHashCode
|
||||||
|
abstract class AbstractHtmlTask extends AbstractTask implements HtmlTask {
|
||||||
|
|
||||||
|
final String path
|
||||||
|
private final File buildDir
|
||||||
|
|
||||||
|
AbstractHtmlTask(String name, String path, File buildDir) {
|
||||||
|
super(name)
|
||||||
|
this.path = path
|
||||||
|
this.buildDir = buildDir
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Result<String> transform(Collection<Task> allTasks)
|
||||||
|
|
||||||
|
@Override
|
||||||
|
final Collection<Diagnostic> execute(Collection<Task> allTasks) {
|
||||||
|
def transformResult = this.transform(allTasks)
|
||||||
|
if (transformResult.hasDiagnostics()) {
|
||||||
|
transformResult.diagnostics
|
||||||
|
} else {
|
||||||
|
def content = transformResult.get()
|
||||||
|
def target = new File(this.buildDir, this.path)
|
||||||
|
target.createParentDirectories()
|
||||||
|
target.write(content)
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"AbstractHtmlTask(path: ${ this.path }, super: ${ super.toString() })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.jessebrault.ssg.html
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.task.Task
|
||||||
|
|
||||||
|
interface HtmlTask extends Task {
|
||||||
|
String getPath()
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.jessebrault.ssg.html
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.model.Model
|
||||||
|
import com.jessebrault.ssg.template.Template
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@TupleConstructor(defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class ModelToHtmlSpec<T> {
|
||||||
|
final Model<T> model
|
||||||
|
final Template template
|
||||||
|
final String path
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
package com.jessebrault.ssg.html
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.SiteSpec
|
||||||
|
import com.jessebrault.ssg.model.Model
|
||||||
|
import com.jessebrault.ssg.part.Part
|
||||||
|
import com.jessebrault.ssg.render.RenderContext
|
||||||
|
import com.jessebrault.ssg.task.Task
|
||||||
|
import com.jessebrault.ssg.task.TaskSpec
|
||||||
|
import com.jessebrault.ssg.template.Template
|
||||||
|
import com.jessebrault.ssg.text.Text
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
|
||||||
|
@NullCheck
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class ModelToHtmlTask<T> extends AbstractHtmlTask {
|
||||||
|
|
||||||
|
private final SiteSpec siteSpec
|
||||||
|
private final Map<String, Object> globals
|
||||||
|
private final Model<T> model
|
||||||
|
private final Template template
|
||||||
|
private final Collection<Text> allTexts
|
||||||
|
private final Collection<Model<Object>> allModels
|
||||||
|
private final Collection<Part> allParts
|
||||||
|
|
||||||
|
ModelToHtmlTask(
|
||||||
|
String path,
|
||||||
|
TaskSpec taskSpec,
|
||||||
|
Model<T> model,
|
||||||
|
Template template,
|
||||||
|
Collection<Text> allTexts,
|
||||||
|
Collection<Model<Object>> allModels,
|
||||||
|
Collection<Part> allParts
|
||||||
|
) {
|
||||||
|
super("modelToHtml:${ path }", path, taskSpec.outputDir)
|
||||||
|
this.siteSpec = taskSpec.siteSpec
|
||||||
|
this.globals = taskSpec.globals
|
||||||
|
this.model = model
|
||||||
|
this.template = template
|
||||||
|
this.allTexts = allTexts
|
||||||
|
this.allModels = allModels
|
||||||
|
this.allParts = allParts
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Result<String> transform(Collection<Task> allTasks) {
|
||||||
|
this.template.type.renderer.render(this.template, null, new RenderContext(
|
||||||
|
sourcePath: this.model.name,
|
||||||
|
targetPath: this.path,
|
||||||
|
tasks: allTasks,
|
||||||
|
texts: this.allTexts,
|
||||||
|
models: this.allModels,
|
||||||
|
parts: this.allParts,
|
||||||
|
siteSpec: this.siteSpec,
|
||||||
|
globals: this.globals
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"ModelToHtml(${ this.model }, ${ this.template }, ${ this.allTexts }, ${ this.allParts }, ${ super.toString() })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.jessebrault.ssg.html
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProvider
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProviders
|
||||||
|
import com.jessebrault.ssg.task.AbstractRenderTaskFactory
|
||||||
|
import com.jessebrault.ssg.task.Task
|
||||||
|
import com.jessebrault.ssg.task.TaskSpec
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
|
||||||
|
final class ModelToHtmlTaskFactory<T> extends AbstractRenderTaskFactory {
|
||||||
|
|
||||||
|
CollectionProvider<ModelToHtmlSpec<T>> specsProvider = CollectionProviders.getEmpty()
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Result<Collection<Task>> getTasks(TaskSpec taskSpec) {
|
||||||
|
def allTexts = this.allTextsProvider.provide()
|
||||||
|
def allModels = this.allModelsProvider.provide()
|
||||||
|
def allParts = this.allPartsProvider.provide()
|
||||||
|
|
||||||
|
Result.of(specsProvider.provide().collect {
|
||||||
|
new ModelToHtmlTask<>(
|
||||||
|
it.path,
|
||||||
|
taskSpec,
|
||||||
|
it.model,
|
||||||
|
it.template,
|
||||||
|
allTexts,
|
||||||
|
allModels,
|
||||||
|
allParts
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
package com.jessebrault.ssg.html
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.SiteSpec
|
||||||
|
import com.jessebrault.ssg.model.Model
|
||||||
|
import com.jessebrault.ssg.page.Page
|
||||||
|
import com.jessebrault.ssg.part.Part
|
||||||
|
import com.jessebrault.ssg.render.RenderContext
|
||||||
|
import com.jessebrault.ssg.task.Task
|
||||||
|
import com.jessebrault.ssg.task.TaskSpec
|
||||||
|
import com.jessebrault.ssg.text.Text
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
|
||||||
|
@NullCheck
|
||||||
|
@EqualsAndHashCode(includeFields = true, callSuper = true)
|
||||||
|
final class PageToHtmlTask extends AbstractHtmlTask {
|
||||||
|
|
||||||
|
private final SiteSpec siteSpec
|
||||||
|
private final Map<String, Object> globals
|
||||||
|
private final Page page
|
||||||
|
private final Collection<Text> allTexts
|
||||||
|
private final Collection<Model<Object>> allModels
|
||||||
|
private final Collection<Part> allParts
|
||||||
|
|
||||||
|
PageToHtmlTask(
|
||||||
|
String path,
|
||||||
|
TaskSpec taskSpec,
|
||||||
|
Page page,
|
||||||
|
Collection<Text> allTexts,
|
||||||
|
Collection<Model<Object>> allModels,
|
||||||
|
Collection<Part> allParts
|
||||||
|
) {
|
||||||
|
super("pageToHtml:${ path }", path, taskSpec.outputDir)
|
||||||
|
this.siteSpec = taskSpec.siteSpec
|
||||||
|
this.globals = taskSpec.globals
|
||||||
|
this.page = page
|
||||||
|
this.allTexts = allTexts
|
||||||
|
this.allModels = allModels
|
||||||
|
this.allParts = allParts
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Result<String> transform(Collection<Task> allTasks) {
|
||||||
|
this.page.type.renderer.render(this.page, new RenderContext(
|
||||||
|
this.page.path,
|
||||||
|
this.path,
|
||||||
|
allTasks,
|
||||||
|
this.allTexts,
|
||||||
|
this.allModels,
|
||||||
|
this.allParts,
|
||||||
|
this.siteSpec,
|
||||||
|
this.globals
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"PageToHtml(${ this.page }, ${ this.allTexts }, ${ this.allParts }, ${ super.toString() })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package com.jessebrault.ssg.html
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.page.Page
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProvider
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProviders
|
||||||
|
import com.jessebrault.ssg.task.AbstractRenderTaskFactory
|
||||||
|
import com.jessebrault.ssg.task.Task
|
||||||
|
import com.jessebrault.ssg.task.TaskSpec
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
|
||||||
|
import static com.jessebrault.ssg.util.ExtensionUtil.stripExtension
|
||||||
|
import static java.util.Objects.requireNonNull
|
||||||
|
|
||||||
|
final class PageToHtmlTaskFactory extends AbstractRenderTaskFactory {
|
||||||
|
|
||||||
|
CollectionProvider<Page> pagesProvider = CollectionProviders.getEmpty()
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Result<Collection<Task>> getTasks(TaskSpec taskSpec) {
|
||||||
|
super.checkProviders()
|
||||||
|
requireNonNull(this.pagesProvider)
|
||||||
|
|
||||||
|
def allTexts = this.allTextsProvider.provide()
|
||||||
|
def allModels = this.allModelsProvider.provide()
|
||||||
|
def allParts = this.allPartsProvider.provide()
|
||||||
|
|
||||||
|
final Collection<Task> tasks = this.pagesProvider.provide()
|
||||||
|
.collect {
|
||||||
|
new PageToHtmlTask(
|
||||||
|
stripExtension(it.path) + '.html',
|
||||||
|
taskSpec,
|
||||||
|
it,
|
||||||
|
allTexts,
|
||||||
|
allModels,
|
||||||
|
allParts
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Result.of(tasks)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.jessebrault.ssg.html
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.template.Template
|
||||||
|
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 TextToHtmlSpec {
|
||||||
|
final Text text
|
||||||
|
final Template template
|
||||||
|
final String path
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
package com.jessebrault.ssg.html
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.SiteSpec
|
||||||
|
import com.jessebrault.ssg.model.Model
|
||||||
|
import com.jessebrault.ssg.part.Part
|
||||||
|
import com.jessebrault.ssg.render.RenderContext
|
||||||
|
import com.jessebrault.ssg.task.Task
|
||||||
|
import com.jessebrault.ssg.task.TaskSpec
|
||||||
|
import com.jessebrault.ssg.template.Template
|
||||||
|
import com.jessebrault.ssg.text.Text
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
|
||||||
|
@NullCheck
|
||||||
|
@EqualsAndHashCode(includeFields = true, callSuper = true)
|
||||||
|
final class TextToHtmlTask extends AbstractHtmlTask {
|
||||||
|
|
||||||
|
private final SiteSpec siteSpec
|
||||||
|
private final Map<String, Object> globals
|
||||||
|
private final Text text
|
||||||
|
private final Template template
|
||||||
|
private final Collection<Text> allTexts
|
||||||
|
private final Collection<Model<Object>> allModels
|
||||||
|
private final Collection<Part> allParts
|
||||||
|
|
||||||
|
TextToHtmlTask(
|
||||||
|
String path,
|
||||||
|
TaskSpec taskSpec,
|
||||||
|
Text text,
|
||||||
|
Template template,
|
||||||
|
Collection<Text> allTexts,
|
||||||
|
Collection<Model<Object>> allModels,
|
||||||
|
Collection<Part> allParts
|
||||||
|
) {
|
||||||
|
super("textToHtml:${ path }", path, taskSpec.outputDir)
|
||||||
|
this.siteSpec = taskSpec.siteSpec
|
||||||
|
this.globals = taskSpec.globals
|
||||||
|
this.text = text
|
||||||
|
this.template = template
|
||||||
|
this.allTexts = allTexts
|
||||||
|
this.allModels = allModels
|
||||||
|
this.allParts = allParts
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Result<String> transform(Collection<Task> allTasks) {
|
||||||
|
this.template.type.renderer.render(this.template, this.text, new RenderContext(
|
||||||
|
this.text.path,
|
||||||
|
this.path,
|
||||||
|
allTasks,
|
||||||
|
this.allTexts,
|
||||||
|
this.allModels,
|
||||||
|
this.allParts,
|
||||||
|
this.siteSpec,
|
||||||
|
this.globals
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"TextToHtml(${ this.text }, ${ this.template }, ${ this.allTexts }, ${ this.allParts }, ${ super.toString() })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package com.jessebrault.ssg.html
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProvider
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProviders
|
||||||
|
import com.jessebrault.ssg.task.AbstractRenderTaskFactory
|
||||||
|
import com.jessebrault.ssg.task.Task
|
||||||
|
import com.jessebrault.ssg.task.TaskSpec
|
||||||
|
import com.jessebrault.ssg.util.Diagnostic
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull
|
||||||
|
|
||||||
|
final class TextToHtmlTaskFactory extends AbstractRenderTaskFactory {
|
||||||
|
|
||||||
|
CollectionProvider<Result<TextToHtmlSpec>> specProvider = CollectionProviders.getEmpty()
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Result<Collection<Task>> getTasks(TaskSpec taskSpec) {
|
||||||
|
super.checkProviders()
|
||||||
|
requireNonNull(this.specProvider)
|
||||||
|
|
||||||
|
def allTexts = this.allTextsProvider.provide()
|
||||||
|
def allModels = this.allModelsProvider.provide()
|
||||||
|
def allParts = this.allPartsProvider.provide()
|
||||||
|
|
||||||
|
Collection<Diagnostic> diagnostics = []
|
||||||
|
|
||||||
|
final Collection<Task> tasks = this.specProvider.provide()
|
||||||
|
.findResults {
|
||||||
|
if (it.hasDiagnostics()) {
|
||||||
|
diagnostics.addAll(it.diagnostics)
|
||||||
|
} else {
|
||||||
|
def spec = it.get()
|
||||||
|
new TextToHtmlTask(
|
||||||
|
spec.path,
|
||||||
|
taskSpec,
|
||||||
|
spec.text,
|
||||||
|
spec.template,
|
||||||
|
allTexts,
|
||||||
|
allModels,
|
||||||
|
allParts
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Result.of(diagnostics, tasks)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package com.jessebrault.ssg.model
|
||||||
|
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.PackageScope
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@PackageScope
|
||||||
|
@TupleConstructor(includeFields = true, defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode(includeFields = true)
|
||||||
|
final class ClosureBasedModel<T> implements Model<T> {
|
||||||
|
|
||||||
|
final String name
|
||||||
|
private final Closure<T> tClosure
|
||||||
|
|
||||||
|
@Override
|
||||||
|
T get() {
|
||||||
|
this.tClosure()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package com.jessebrault.ssg.model
|
||||||
|
|
||||||
|
interface Model<T> {
|
||||||
|
String getName()
|
||||||
|
T get()
|
||||||
|
}
|
46
api/src/main/groovy/com/jessebrault/ssg/model/Models.groovy
Normal file
46
api/src/main/groovy/com/jessebrault/ssg/model/Models.groovy
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package com.jessebrault.ssg.model
|
||||||
|
|
||||||
|
import groovy.io.FileType
|
||||||
|
import groovy.transform.stc.ClosureParams
|
||||||
|
import groovy.transform.stc.FromString
|
||||||
|
|
||||||
|
import java.nio.file.Path
|
||||||
|
|
||||||
|
final class Models {
|
||||||
|
|
||||||
|
static <T> Model<T> of(String name, T t) {
|
||||||
|
new SimpleModel<>(name, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> Model<T> from(String name, Closure<T> tClosure) {
|
||||||
|
new ClosureBasedModel<>(name, tClosure)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a directory and iterates recursively through all files present in the directory and sub-directories,
|
||||||
|
* supplying each File along with a String representing that File's path relative to the base Directory to the
|
||||||
|
* given Closure, which then returns a Model containing T, all of which are collected and then returned together.
|
||||||
|
*
|
||||||
|
* @param directory The base directory in which to search for Files to process.
|
||||||
|
* @param fileToModelClosure A Closure which receives two params: the File being processed,
|
||||||
|
* and a String representing the path of that File relative to the base directory. Must return
|
||||||
|
* a Model containing T.
|
||||||
|
* @return A Collection of Models containing Ts.
|
||||||
|
*/
|
||||||
|
static <T> Collection<Model<T>> fromDirectory(
|
||||||
|
File directory,
|
||||||
|
@ClosureParams(value = FromString, options = ['java.io.File, java.lang.String'])
|
||||||
|
Closure<Model<T>> fileToModelClosure
|
||||||
|
) {
|
||||||
|
final Collection<Model<T>> models = []
|
||||||
|
def directoryPath = Path.of(directory.path)
|
||||||
|
directory.eachFileRecurse(FileType.FILES) {
|
||||||
|
def relativePath = directoryPath.relativize(Path.of(it.path)).toString()
|
||||||
|
models << fileToModelClosure(it, relativePath)
|
||||||
|
}
|
||||||
|
models
|
||||||
|
}
|
||||||
|
|
||||||
|
private Models() {}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.jessebrault.ssg.model
|
||||||
|
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.PackageScope
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@PackageScope
|
||||||
|
@TupleConstructor(includeFields = true, defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode(includeFields = true)
|
||||||
|
final class SimpleModel<T> implements Model<T> {
|
||||||
|
|
||||||
|
final String name
|
||||||
|
private final T t
|
||||||
|
|
||||||
|
@Override
|
||||||
|
T get() {
|
||||||
|
this.t
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"SimpleModel(${ this.t })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package com.jessebrault.ssg.page
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.render.RenderContext
|
||||||
|
import com.jessebrault.ssg.render.StandardGspRenderer
|
||||||
|
import com.jessebrault.ssg.util.Diagnostic
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
|
||||||
|
@NullCheck
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class GspPageRenderer implements PageRenderer {
|
||||||
|
|
||||||
|
private final StandardGspRenderer gspRenderer = new StandardGspRenderer(this.class.classLoader)
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Result<String> render(
|
||||||
|
Page specialPage,
|
||||||
|
RenderContext context
|
||||||
|
) {
|
||||||
|
def diagnostics = []
|
||||||
|
try {
|
||||||
|
def result = this.gspRenderer.render(specialPage.text, context) {
|
||||||
|
it.loggerName = "GspSpecialPage(${ specialPage.path })"
|
||||||
|
it.onDiagnostics = diagnostics.&addAll
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Result.of(diagnostics, result.toString())
|
||||||
|
} catch (Exception e) {
|
||||||
|
Result.of(
|
||||||
|
[*diagnostics, new Diagnostic(
|
||||||
|
"An exception occurred while rendering specialPage ${ specialPage.path }:\n${ e }",
|
||||||
|
e
|
||||||
|
)],
|
||||||
|
''
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"GspSpecialPageRenderer()"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
21
api/src/main/groovy/com/jessebrault/ssg/page/Page.groovy
Normal file
21
api/src/main/groovy/com/jessebrault/ssg/page/Page.groovy
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package com.jessebrault.ssg.page
|
||||||
|
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@TupleConstructor(defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class Page {
|
||||||
|
|
||||||
|
final String path
|
||||||
|
final PageType type
|
||||||
|
final String text
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"Page(path: ${ this.path }, type: ${ this.type })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.jessebrault.ssg.page
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.render.RenderContext
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
|
||||||
|
interface PageRenderer {
|
||||||
|
|
||||||
|
Result<String> render(
|
||||||
|
Page specialPage,
|
||||||
|
RenderContext context
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
20
api/src/main/groovy/com/jessebrault/ssg/page/PageType.groovy
Normal file
20
api/src/main/groovy/com/jessebrault/ssg/page/PageType.groovy
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package com.jessebrault.ssg.page
|
||||||
|
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@TupleConstructor(defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class PageType {
|
||||||
|
|
||||||
|
Collection<String> ids
|
||||||
|
PageRenderer renderer
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"PageType(ids: ${ this.ids }, renderer: ${ this.renderer })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package com.jessebrault.ssg.page
|
||||||
|
|
||||||
|
final class PageTypes {
|
||||||
|
|
||||||
|
static final PageType GSP = new PageType(['.gsp'], new GspPageRenderer())
|
||||||
|
|
||||||
|
private PageTypes() {}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package com.jessebrault.ssg.page
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProvider
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProviders
|
||||||
|
import com.jessebrault.ssg.util.ExtensionUtil
|
||||||
|
import com.jessebrault.ssg.util.PathUtil
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
final class PagesProviders {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(PagesProviders)
|
||||||
|
|
||||||
|
static CollectionProvider<Page> from(File pagesDirectory, Collection<PageType> pageTypes) {
|
||||||
|
CollectionProviders.from(pagesDirectory) {
|
||||||
|
def extension = ExtensionUtil.getExtension(it.path)
|
||||||
|
def pageType = pageTypes.find { it.ids.contains(extension) }
|
||||||
|
if (!pageType) {
|
||||||
|
logger.warn('there is no PageType for file {}; skipping', it)
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
new Page(PathUtil.relative(pagesDirectory.path, it.path), pageType, it.getText())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PagesProviders() {}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,56 @@
|
|||||||
|
package com.jessebrault.ssg.part
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.render.StandardGspRenderer
|
||||||
|
import com.jessebrault.ssg.util.Diagnostic
|
||||||
|
import com.jessebrault.ssg.render.RenderContext
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
import com.jessebrault.ssg.text.Text
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import org.jetbrains.annotations.Nullable
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull
|
||||||
|
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class GspPartRenderer implements PartRenderer {
|
||||||
|
|
||||||
|
private final StandardGspRenderer gspRenderer = new StandardGspRenderer(this.class.classLoader)
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Result<String> render(
|
||||||
|
Part part,
|
||||||
|
Map<String, Object> binding,
|
||||||
|
RenderContext context,
|
||||||
|
@Nullable Text text
|
||||||
|
) {
|
||||||
|
requireNonNull(part)
|
||||||
|
requireNonNull(binding)
|
||||||
|
requireNonNull(context)
|
||||||
|
def diagnostics = []
|
||||||
|
try {
|
||||||
|
def result = this.gspRenderer.render(part.text, context) {
|
||||||
|
it.putCustom('binding', binding)
|
||||||
|
it.loggerName = "GspPart(${ part.path })"
|
||||||
|
it.onDiagnostics = diagnostics.&addAll
|
||||||
|
if (text) {
|
||||||
|
it.text = text
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Result.of(diagnostics, result.toString())
|
||||||
|
} catch (Exception e) {
|
||||||
|
Result.of(
|
||||||
|
[*diagnostics, new Diagnostic(
|
||||||
|
"An exception occurred while rendering part ${ part.path }:\n${ e }",
|
||||||
|
e
|
||||||
|
)],
|
||||||
|
''
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"GspPartRenderer()"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
21
api/src/main/groovy/com/jessebrault/ssg/part/Part.groovy
Normal file
21
api/src/main/groovy/com/jessebrault/ssg/part/Part.groovy
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package com.jessebrault.ssg.part
|
||||||
|
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@TupleConstructor(defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class Part {
|
||||||
|
|
||||||
|
String path
|
||||||
|
PartType type
|
||||||
|
String text
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"Part(path: ${ this.path }, type: ${ this.type })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.jessebrault.ssg.part
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.render.RenderContext
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
import com.jessebrault.ssg.text.Text
|
||||||
|
import org.jetbrains.annotations.Nullable
|
||||||
|
|
||||||
|
interface PartRenderer {
|
||||||
|
|
||||||
|
Result<String> render(
|
||||||
|
Part part,
|
||||||
|
Map<String, Object> binding,
|
||||||
|
RenderContext context,
|
||||||
|
@Nullable Text text
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
20
api/src/main/groovy/com/jessebrault/ssg/part/PartType.groovy
Normal file
20
api/src/main/groovy/com/jessebrault/ssg/part/PartType.groovy
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package com.jessebrault.ssg.part
|
||||||
|
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@TupleConstructor(defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class PartType {
|
||||||
|
|
||||||
|
Collection<String> ids
|
||||||
|
PartRenderer renderer
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"PartType(ids: ${ this.ids }, renderer: ${ this.renderer })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package com.jessebrault.ssg.part
|
||||||
|
|
||||||
|
final class PartTypes {
|
||||||
|
|
||||||
|
static final PartType GSP = new PartType(['.gsp'], new GspPartRenderer())
|
||||||
|
|
||||||
|
private PartTypes() {}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package com.jessebrault.ssg.part
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProvider
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProviders
|
||||||
|
import com.jessebrault.ssg.util.ExtensionUtil
|
||||||
|
import com.jessebrault.ssg.util.PathUtil
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
final class PartsProviders {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(PartsProviders)
|
||||||
|
|
||||||
|
static CollectionProvider<Part> of(File partsDir, Collection<PartType> partTypes) {
|
||||||
|
CollectionProviders.from(partsDir) {
|
||||||
|
def extension = ExtensionUtil.getExtension(it.path)
|
||||||
|
def partType = partTypes.find { it.ids.contains(extension) }
|
||||||
|
if (!partType) {
|
||||||
|
logger.warn('there is no PartType for file {}; skipping', it)
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
new Part(PathUtil.relative(partsDir.path, it.path), partType, it.getText())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PartsProviders() {}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package com.jessebrault.ssg.provider
|
||||||
|
|
||||||
|
abstract class AbstractCollectionProvider<T> implements CollectionProvider<T> {
|
||||||
|
|
||||||
|
static <T> CollectionProvider<T> concat(
|
||||||
|
CollectionProvider<T> cp0,
|
||||||
|
CollectionProvider<T> cp1
|
||||||
|
) {
|
||||||
|
ClosureBasedCollectionProvider.get {
|
||||||
|
cp0.provide() + cp1.provide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
CollectionProvider<T> plus(Provider<T> other) {
|
||||||
|
concat(this, other as CollectionProvider<T>)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
CollectionProvider<T> plus(CollectionProvider<T> other) {
|
||||||
|
concat(this, other)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package com.jessebrault.ssg.provider
|
||||||
|
|
||||||
|
abstract class AbstractProvider<T> implements Provider<T> {
|
||||||
|
|
||||||
|
static <T> CollectionProvider<T> concat(
|
||||||
|
Provider<T> p0,
|
||||||
|
Provider<T> p1
|
||||||
|
) {
|
||||||
|
ClosureBasedCollectionProvider.get {
|
||||||
|
[p0.provide(), p1.provide()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
CollectionProvider<T> plus(Provider<T> other) {
|
||||||
|
concat(this, other)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
CollectionProvider<T> asType(Class<CollectionProvider> collectionProviderClass) {
|
||||||
|
ClosureBasedCollectionProvider.get {
|
||||||
|
[this.provide() as T]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.jessebrault.ssg.provider
|
||||||
|
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.PackageScope
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@PackageScope
|
||||||
|
@TupleConstructor(defaults = false, includeFields = true)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode(includeFields = true)
|
||||||
|
final class ClosureBasedCollectionProvider<T> extends AbstractCollectionProvider<T> {
|
||||||
|
|
||||||
|
static <T> CollectionProvider<T> get(Closure<Collection<T>> closure) {
|
||||||
|
new ClosureBasedCollectionProvider<>(closure)
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Closure<Collection<T>> closure
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Collection<T> provide() {
|
||||||
|
this.closure()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.jessebrault.ssg.provider
|
||||||
|
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.PackageScope
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@PackageScope
|
||||||
|
@TupleConstructor(includeFields = true, defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode(includeFields = true)
|
||||||
|
final class ClosureBasedProvider<T> extends AbstractProvider<T> {
|
||||||
|
|
||||||
|
static <T> Provider<T> of(Closure<T> closure) {
|
||||||
|
new ClosureBasedProvider<>(closure)
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Closure<T> closure
|
||||||
|
|
||||||
|
@Override
|
||||||
|
T provide() {
|
||||||
|
this.closure()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.jessebrault.ssg.provider
|
||||||
|
|
||||||
|
interface CollectionProvider<T> {
|
||||||
|
Collection<T> provide()
|
||||||
|
CollectionProvider<T> plus(Provider<T> other)
|
||||||
|
CollectionProvider<T> plus(CollectionProvider<T> other)
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package com.jessebrault.ssg.provider
|
||||||
|
|
||||||
|
import groovy.transform.stc.ClosureParams
|
||||||
|
import groovy.transform.stc.FromString
|
||||||
|
import org.jetbrains.annotations.Nullable
|
||||||
|
|
||||||
|
final class CollectionProviders {
|
||||||
|
|
||||||
|
static <T> CollectionProvider<T> getEmpty() {
|
||||||
|
new SimpleCollectionProvider<>([])
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> CollectionProvider<T> of(Collection<T> ts) {
|
||||||
|
new SimpleCollectionProvider<T>(ts)
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> CollectionProvider<T> from(Closure<Collection<T>> closure) {
|
||||||
|
ClosureBasedCollectionProvider.get(closure)
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> CollectionProvider<T> from(
|
||||||
|
File dir,
|
||||||
|
@ClosureParams(value = FromString, options = 'java.io.File')
|
||||||
|
Closure<@Nullable T> fileToElementClosure
|
||||||
|
) {
|
||||||
|
new FileBasedCollectionProvider<>(dir, fileToElementClosure)
|
||||||
|
}
|
||||||
|
|
||||||
|
private CollectionProviders() {}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package com.jessebrault.ssg.provider
|
||||||
|
|
||||||
|
import groovy.io.FileType
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.PackageScope
|
||||||
|
import groovy.transform.stc.ClosureParams
|
||||||
|
import groovy.transform.stc.FromString
|
||||||
|
import org.jetbrains.annotations.Nullable
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
@PackageScope
|
||||||
|
@NullCheck
|
||||||
|
@EqualsAndHashCode(includeFields = true)
|
||||||
|
final class FileBasedCollectionProvider<T> extends AbstractCollectionProvider<T> {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(FileBasedCollectionProvider)
|
||||||
|
|
||||||
|
private final File dir
|
||||||
|
private final Closure<@Nullable T> fileToElementClosure
|
||||||
|
|
||||||
|
FileBasedCollectionProvider(
|
||||||
|
File dir,
|
||||||
|
@ClosureParams(value = FromString, options = 'java.io.File')
|
||||||
|
Closure<@Nullable T> fileToElementClosure
|
||||||
|
) {
|
||||||
|
this.dir = dir
|
||||||
|
this.fileToElementClosure = fileToElementClosure
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Collection<T> provide() {
|
||||||
|
if (!this.dir.isDirectory()) {
|
||||||
|
logger.error('{} does not exist or is not a directory; returning empty collection', this.dir)
|
||||||
|
[]
|
||||||
|
} else {
|
||||||
|
def ts = []
|
||||||
|
this.dir.eachFileRecurse(FileType.FILES) {
|
||||||
|
def t = this.fileToElementClosure(it)
|
||||||
|
if (t) {
|
||||||
|
ts << t
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.jessebrault.ssg.provider
|
||||||
|
|
||||||
|
interface Provider<T> {
|
||||||
|
T provide()
|
||||||
|
CollectionProvider<T> plus(Provider<T> other)
|
||||||
|
CollectionProvider<T> asType(Class<CollectionProvider> collectionProviderClass)
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.jessebrault.ssg.provider
|
||||||
|
|
||||||
|
import org.codehaus.groovy.runtime.InvokerHelper
|
||||||
|
|
||||||
|
final class Providers {
|
||||||
|
|
||||||
|
static <T> Provider<T> of(T t) {
|
||||||
|
new SimpleProvider<>(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> Provider<T> from(Closure<T> closure) {
|
||||||
|
ClosureBasedProvider.of(closure)
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> CollectionProvider<T> concat(Provider<T> ...providers) {
|
||||||
|
concat(List.of(providers))
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> CollectionProvider<T> concat(Collection<Provider<T>> providers) {
|
||||||
|
providers.inject(CollectionProviders.<T>getEmpty()) { acc, val ->
|
||||||
|
acc + val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Providers() {}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package com.jessebrault.ssg.provider
|
||||||
|
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.PackageScope
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@PackageScope
|
||||||
|
@TupleConstructor(defaults = false, includeFields = true)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode(includeFields = true)
|
||||||
|
final class SimpleCollectionProvider<T> extends AbstractCollectionProvider<T> {
|
||||||
|
|
||||||
|
private final Collection<T> ts
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Collection<T> provide() {
|
||||||
|
this.ts
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package com.jessebrault.ssg.provider
|
||||||
|
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.PackageScope
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@PackageScope
|
||||||
|
@TupleConstructor(defaults = false, includeFields = true)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode(includeFields = true)
|
||||||
|
final class SimpleProvider<T> extends AbstractProvider<T> {
|
||||||
|
|
||||||
|
private final T t
|
||||||
|
|
||||||
|
@Override
|
||||||
|
T provide() {
|
||||||
|
this.t
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
package com.jessebrault.ssg.render
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.SiteSpec
|
||||||
|
import com.jessebrault.ssg.model.Model
|
||||||
|
import com.jessebrault.ssg.part.Part
|
||||||
|
import com.jessebrault.ssg.task.Task
|
||||||
|
import com.jessebrault.ssg.text.Text
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@TupleConstructor(defaults = false, force = true)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class RenderContext {
|
||||||
|
|
||||||
|
RenderContext(Map<String, Object> args = [:]) {
|
||||||
|
this(
|
||||||
|
args.sourcePath as String ?: '',
|
||||||
|
args.targetPath as String ?: '',
|
||||||
|
args.tasks as Collection<Task> ?: [],
|
||||||
|
args.texts as Collection<Text> ?: [],
|
||||||
|
args.models as Collection<Model<Object>> ?: [],
|
||||||
|
args.parts as Collection<Part> ?: [],
|
||||||
|
args.siteSpec as SiteSpec ?: SiteSpec.getBlank(),
|
||||||
|
args.globals as Map<String, Object> ?: [:]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
final String sourcePath
|
||||||
|
final String targetPath
|
||||||
|
final Collection<Task> tasks
|
||||||
|
final Collection<Text> texts
|
||||||
|
final Collection<Model<Object>> models
|
||||||
|
final Collection<Part> parts
|
||||||
|
final SiteSpec siteSpec
|
||||||
|
final Map<String, Object> globals
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.jessebrault.ssg.render
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.dsl.StandardDslMap
|
||||||
|
import groovy.text.GStringTemplateEngine
|
||||||
|
import groovy.text.TemplateEngine
|
||||||
|
import org.codehaus.groovy.control.CompilerConfiguration
|
||||||
|
|
||||||
|
final class StandardGspRenderer {
|
||||||
|
|
||||||
|
private final TemplateEngine engine
|
||||||
|
|
||||||
|
StandardGspRenderer(ClassLoader parentClassLoader) {
|
||||||
|
def cc = new CompilerConfiguration() // TODO: investigate if this makes any difference on the ultimate template
|
||||||
|
def gcl = new GroovyClassLoader(parentClassLoader, cc)
|
||||||
|
this.engine = new GStringTemplateEngine(gcl)
|
||||||
|
}
|
||||||
|
|
||||||
|
String render(
|
||||||
|
String template,
|
||||||
|
RenderContext context,
|
||||||
|
@DelegatesTo(value = StandardDslMap.Builder, strategy = Closure.DELEGATE_FIRST)
|
||||||
|
Closure<Void> dslMapBuilderClosure
|
||||||
|
) {
|
||||||
|
this.engine.createTemplate(template).make(StandardDslMap.get(context, dslMapBuilderClosure)).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package com.jessebrault.ssg.task
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.model.Model
|
||||||
|
import com.jessebrault.ssg.part.Part
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProvider
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProviders
|
||||||
|
import com.jessebrault.ssg.text.Text
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull
|
||||||
|
|
||||||
|
abstract class AbstractRenderTaskFactory implements TaskFactory {
|
||||||
|
|
||||||
|
CollectionProvider<Text> allTextsProvider = CollectionProviders.getEmpty()
|
||||||
|
CollectionProvider<Model<Object>> allModelsProvider = CollectionProviders.getEmpty()
|
||||||
|
CollectionProvider<Part> allPartsProvider = CollectionProviders.getEmpty()
|
||||||
|
|
||||||
|
protected final void checkProviders() {
|
||||||
|
requireNonNull(this.allTextsProvider)
|
||||||
|
requireNonNull(this.allModelsProvider)
|
||||||
|
requireNonNull(this.allPartsProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package com.jessebrault.ssg.task
|
||||||
|
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@TupleConstructor(defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode
|
||||||
|
abstract class AbstractTask implements Task {
|
||||||
|
|
||||||
|
final String name
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"AbstractTask(name: ${ this.name })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package com.jessebrault.ssg.task
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.PackageScope
|
||||||
|
import groovy.transform.stc.ClosureParams
|
||||||
|
import groovy.transform.stc.SimpleType
|
||||||
|
|
||||||
|
@PackageScope
|
||||||
|
@NullCheck
|
||||||
|
@EqualsAndHashCode(includeFields = true)
|
||||||
|
final class ClosureBasedTaskFactory implements TaskFactory {
|
||||||
|
|
||||||
|
private final Closure<Collection<Task>> closure
|
||||||
|
|
||||||
|
ClosureBasedTaskFactory(
|
||||||
|
@ClosureParams(value = SimpleType, options = 'com.jessebrault.ssg.task.TaskSpec')
|
||||||
|
Closure<Collection<Task>> closure
|
||||||
|
) {
|
||||||
|
this.closure = closure
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Result<Collection<Task>> getTasks(TaskSpec taskSpec) {
|
||||||
|
this.closure(taskSpec)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package com.jessebrault.ssg.task
|
||||||
|
|
||||||
|
trait RenderingTaskFactory {
|
||||||
|
|
||||||
|
}
|
8
api/src/main/groovy/com/jessebrault/ssg/task/Task.groovy
Normal file
8
api/src/main/groovy/com/jessebrault/ssg/task/Task.groovy
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package com.jessebrault.ssg.task
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.util.Diagnostic
|
||||||
|
|
||||||
|
interface Task {
|
||||||
|
String getName()
|
||||||
|
Collection<Diagnostic> execute(Collection<Task> allTasks)
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.jessebrault.ssg.task
|
||||||
|
|
||||||
|
import groovy.transform.stc.ClosureParams
|
||||||
|
import groovy.transform.stc.SimpleType
|
||||||
|
|
||||||
|
final class TaskFactories {
|
||||||
|
|
||||||
|
static TaskFactory of(
|
||||||
|
@ClosureParams(value = SimpleType, options = 'com.jessebrault.ssg.task.TaskSpec')
|
||||||
|
Closure<Collection<Void>> closure
|
||||||
|
) {
|
||||||
|
new ClosureBasedTaskFactory(closure)
|
||||||
|
}
|
||||||
|
|
||||||
|
private TaskFactories() {}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.jessebrault.ssg.task
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
|
||||||
|
interface TaskFactory {
|
||||||
|
Result<Collection<Task>> getTasks(TaskSpec taskSpec)
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package com.jessebrault.ssg.task
|
||||||
|
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
import java.util.function.Supplier
|
||||||
|
|
||||||
|
@TupleConstructor(defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class TaskFactorySpec {
|
||||||
|
|
||||||
|
static TaskFactorySpec concat(TaskFactorySpec spec0, TaskFactorySpec spec1) {
|
||||||
|
if (spec0.supplier != spec1.supplier) {
|
||||||
|
throw new IllegalArgumentException("suppliers must be equal!")
|
||||||
|
}
|
||||||
|
new TaskFactorySpec(spec0.supplier, spec0.configureClosures + spec1.configureClosures)
|
||||||
|
}
|
||||||
|
|
||||||
|
final Supplier<TaskFactory> supplier
|
||||||
|
final Collection<Closure<Void>> configureClosures
|
||||||
|
|
||||||
|
TaskFactorySpec plus(TaskFactorySpec other) {
|
||||||
|
concat(this, other)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
22
api/src/main/groovy/com/jessebrault/ssg/task/TaskSpec.groovy
Normal file
22
api/src/main/groovy/com/jessebrault/ssg/task/TaskSpec.groovy
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package com.jessebrault.ssg.task
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.SiteSpec
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@TupleConstructor(defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class TaskSpec {
|
||||||
|
|
||||||
|
static TaskSpec getEmpty() {
|
||||||
|
new TaskSpec('', new File(''), SiteSpec.getBlank(), [:])
|
||||||
|
}
|
||||||
|
|
||||||
|
final String buildName
|
||||||
|
final File outputDir
|
||||||
|
final SiteSpec siteSpec
|
||||||
|
final Map<String, Object> globals
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
package com.jessebrault.ssg.task.collector
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.provider.Provider
|
||||||
|
import com.jessebrault.ssg.task.TaskFactory
|
||||||
|
import groovy.io.FileType
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@TupleConstructor(defaults = false, includeFields = true)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
final class GroovyFileTaskFactoryCollector implements TaskFactoryCollector {
|
||||||
|
|
||||||
|
private final GroovyClassLoader groovyClassLoader
|
||||||
|
private final Collection<Provider<File>> factoryDirectoryProviders
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Collection<TaskFactory> getAllFactories() {
|
||||||
|
Collection<TaskFactory> factories = []
|
||||||
|
|
||||||
|
def pluginDirectories = this.factoryDirectoryProviders.collect { it.provide() }
|
||||||
|
pluginDirectories.each {
|
||||||
|
it.eachFileRecurse(FileType.FILES) {
|
||||||
|
def cl = this.groovyClassLoader.parseClass(it)
|
||||||
|
if (TaskFactory.isAssignableFrom(cl)) {
|
||||||
|
def constructor = cl.getDeclaredConstructor()
|
||||||
|
def factory = constructor.newInstance() as TaskFactory
|
||||||
|
factories << factory
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
factories
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.jessebrault.ssg.task.collector
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.task.TaskFactory
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@TupleConstructor(includeFields = true, defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
final class ServiceTaskFactoryCollector implements TaskFactoryCollector {
|
||||||
|
|
||||||
|
private final ClassLoader classLoader
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Collection<TaskFactory> getAllFactories() {
|
||||||
|
ServiceLoader.load(TaskFactory, this.classLoader).asList()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.jessebrault.ssg.task.collector
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.task.TaskFactory
|
||||||
|
|
||||||
|
interface TaskFactoryCollector {
|
||||||
|
Collection<TaskFactory> getAllFactories()
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package com.jessebrault.ssg.template
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.render.StandardGspRenderer
|
||||||
|
import com.jessebrault.ssg.util.Diagnostic
|
||||||
|
import com.jessebrault.ssg.render.RenderContext
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
import com.jessebrault.ssg.text.Text
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
|
||||||
|
@NullCheck
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class GspTemplateRenderer implements TemplateRenderer {
|
||||||
|
|
||||||
|
private final StandardGspRenderer gspRenderer = new StandardGspRenderer(this.class.classLoader)
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Result<String> render(
|
||||||
|
Template template,
|
||||||
|
Text text,
|
||||||
|
RenderContext context
|
||||||
|
) {
|
||||||
|
def diagnostics = []
|
||||||
|
try {
|
||||||
|
def result = this.gspRenderer.render(template.text, context) {
|
||||||
|
it.loggerName = "GspTemplate(${ template.path })"
|
||||||
|
it.onDiagnostics = diagnostics.&addAll
|
||||||
|
it.text = text
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Result.of(diagnostics, result)
|
||||||
|
} catch (Exception e) {
|
||||||
|
Result.of(
|
||||||
|
[*diagnostics, new Diagnostic(
|
||||||
|
"An exception occurred while rendering Template ${ template.path }:\n${ e }",
|
||||||
|
e
|
||||||
|
)],
|
||||||
|
''
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"GspTemplateRenderer()"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package com.jessebrault.ssg.template
|
||||||
|
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@TupleConstructor(defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class Template {
|
||||||
|
|
||||||
|
String path
|
||||||
|
TemplateType type
|
||||||
|
String text
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"Template(path: ${ this.path }, type: ${ this.type })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.jessebrault.ssg.template
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.render.RenderContext
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
import com.jessebrault.ssg.text.Text
|
||||||
|
|
||||||
|
interface TemplateRenderer {
|
||||||
|
|
||||||
|
Result<String> render(
|
||||||
|
Template template,
|
||||||
|
Text text,
|
||||||
|
RenderContext context
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.jessebrault.ssg.template
|
||||||
|
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@TupleConstructor(defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class TemplateType {
|
||||||
|
|
||||||
|
Collection<String> ids
|
||||||
|
TemplateRenderer renderer
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"TemplateType(ids: ${ this.ids }, renderer: ${ this.renderer })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
package com.jessebrault.ssg.template
|
||||||
|
|
||||||
|
final class TemplateTypes {
|
||||||
|
|
||||||
|
static final TemplateType GSP = new TemplateType(['.gsp'], new GspTemplateRenderer())
|
||||||
|
|
||||||
|
private TemplateTypes() {}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package com.jessebrault.ssg.template
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProvider
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProviders
|
||||||
|
import com.jessebrault.ssg.util.ExtensionUtil
|
||||||
|
import com.jessebrault.ssg.util.PathUtil
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
final class TemplatesProviders {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(TemplatesProviders)
|
||||||
|
|
||||||
|
static CollectionProvider<Template> from(File templatesDir, Collection<TemplateType> templateTypes) {
|
||||||
|
CollectionProviders.from(templatesDir) {
|
||||||
|
def extension = ExtensionUtil.getExtension(it.path)
|
||||||
|
def templateType = templateTypes.find { it.ids.contains(extension) }
|
||||||
|
if (!templateType) {
|
||||||
|
logger.warn('there is no TemplateType for file {}; skipping', it)
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
new Template(PathUtil.relative(templatesDir.path, it.path), templateType, it.getText())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TemplatesProviders() {}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.jessebrault.ssg.text
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
|
||||||
|
interface ExcerptGetter {
|
||||||
|
Result<String> getExcerpt(Text text, int limit)
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
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(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode(includeFields = true)
|
||||||
|
final 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 })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.jessebrault.ssg.text
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
|
||||||
|
interface FrontMatterGetter {
|
||||||
|
Result<FrontMatter> get(Text text)
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package com.jessebrault.ssg.text
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.util.Diagnostic
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
import org.commonmark.ext.front.matter.YamlFrontMatterExtension
|
||||||
|
import org.commonmark.node.AbstractVisitor
|
||||||
|
import org.commonmark.parser.Parser
|
||||||
|
|
||||||
|
final 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
|
||||||
|
Result<String> getExcerpt(Text text, int limit) {
|
||||||
|
try {
|
||||||
|
def node = parser.parse(text.text)
|
||||||
|
def visitor = new ExcerptVisitor(limit)
|
||||||
|
node.accept(visitor)
|
||||||
|
Result.of(visitor.result)
|
||||||
|
} catch (Exception e) {
|
||||||
|
def diagnostic = new Diagnostic(
|
||||||
|
"There was an exception while getting the excerpt for ${ text }:\n${ e }",
|
||||||
|
e
|
||||||
|
)
|
||||||
|
Result.of([diagnostic], '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package com.jessebrault.ssg.text
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.util.Diagnostic
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
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
|
||||||
|
final class MarkdownFrontMatterGetter implements FrontMatterGetter {
|
||||||
|
|
||||||
|
private static final Parser parser = Parser.builder()
|
||||||
|
.extensions([YamlFrontMatterExtension.create()])
|
||||||
|
.build()
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Result<FrontMatter> get(Text text) {
|
||||||
|
try {
|
||||||
|
def node = parser.parse(text.text)
|
||||||
|
def v = new YamlFrontMatterVisitor()
|
||||||
|
node.accept(v)
|
||||||
|
Result.of(new FrontMatter(text, v.data))
|
||||||
|
} catch (Exception e) {
|
||||||
|
Result.of(
|
||||||
|
[new Diagnostic(
|
||||||
|
"An exception occured while parsing frontMatter for ${ text.path }:\n${ e }",
|
||||||
|
e
|
||||||
|
)],
|
||||||
|
new FrontMatter(text, [:])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"MarkdownFrontMatterGetter()"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package com.jessebrault.ssg.text
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.util.Diagnostic
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
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
|
||||||
|
final class MarkdownTextRenderer implements TextRenderer {
|
||||||
|
|
||||||
|
private static final Parser parser = Parser.builder()
|
||||||
|
.extensions([YamlFrontMatterExtension.create()])
|
||||||
|
.build()
|
||||||
|
private static final HtmlRenderer htmlRenderer = HtmlRenderer.builder().build()
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Result<String> render(Text text) {
|
||||||
|
try {
|
||||||
|
Result.of(htmlRenderer.render(parser.parse(text.text)))
|
||||||
|
} catch (Exception e) {
|
||||||
|
Result.of(
|
||||||
|
[new Diagnostic("There was an exception while rendering ${ text.path }:\n${ e }", e)],
|
||||||
|
''
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"MarkdownTextRenderer()"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
21
api/src/main/groovy/com/jessebrault/ssg/text/Text.groovy
Normal file
21
api/src/main/groovy/com/jessebrault/ssg/text/Text.groovy
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
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 path
|
||||||
|
final TextType type
|
||||||
|
final String text
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"Text(path: ${ this.path }, type: ${ this.type })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.jessebrault.ssg.text
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
|
||||||
|
interface TextRenderer {
|
||||||
|
Result<String> render(Text text)
|
||||||
|
}
|
22
api/src/main/groovy/com/jessebrault/ssg/text/TextType.groovy
Normal file
22
api/src/main/groovy/com/jessebrault/ssg/text/TextType.groovy
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
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 TextType {
|
||||||
|
|
||||||
|
final Collection<String> ids
|
||||||
|
final TextRenderer renderer
|
||||||
|
final FrontMatterGetter frontMatterGetter
|
||||||
|
final ExcerptGetter excerptGetter
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"TextType(ids: ${ this.ids })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.jessebrault.ssg.text
|
||||||
|
|
||||||
|
final class TextTypes {
|
||||||
|
|
||||||
|
static final MARKDOWN = new TextType(
|
||||||
|
['.md'],
|
||||||
|
new MarkdownTextRenderer(),
|
||||||
|
new MarkdownFrontMatterGetter(),
|
||||||
|
new MarkdownExcerptGetter()
|
||||||
|
)
|
||||||
|
|
||||||
|
private TextTypes() {}
|
||||||
|
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user