Compare commits
No commits in common. "master" and "v0.2.0-SNAPSHOT" have entirely different histories.
master
...
v0.2.0-SNA
@ -1,28 +0,0 @@
|
|||||||
name: Ssg Check, Publish, and Release
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- v*
|
|
||||||
jobs:
|
|
||||||
ci:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout the code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Setup Java.
|
|
||||||
uses: actions/setup-java@v4
|
|
||||||
with:
|
|
||||||
distribution: 'temurin'
|
|
||||||
java-version: 21
|
|
||||||
- name: Check libraries
|
|
||||||
run: ./gradlew check
|
|
||||||
- name: Publish to git.jessebrault.com
|
|
||||||
run: ./gradlew publishAllPublicationsToGiteaRepository
|
|
||||||
- name: Create install distribution
|
|
||||||
run: ./gradlew installDist
|
|
||||||
- name: Release cli to git.jessebrault.com
|
|
||||||
uses: akkuman/gitea-release-action@v1
|
|
||||||
with:
|
|
||||||
files: |-
|
|
||||||
cli/build/distributions/*.tar
|
|
||||||
cli/build/distributions/*.zip
|
|
29
.github/workflows/release.yml
vendored
Normal file
29
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
name: StaticSiteGenerator Release
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*'
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
- name: Set up JDK 17
|
||||||
|
uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
java-version: 17
|
||||||
|
distribution: adopt
|
||||||
|
cache: gradle
|
||||||
|
- name: Gradle Test
|
||||||
|
run: ./gradlew test
|
||||||
|
- name: Gradle Install
|
||||||
|
run: ./gradlew :cli:assembleDist
|
||||||
|
- name: Release
|
||||||
|
uses: ncipollo/release-action@v1
|
||||||
|
with:
|
||||||
|
artifacts: 'cli/build/distributions/*.tar,cli/build/distributions/*.zip'
|
||||||
|
name: ${{ env.GITHUB_REF_NAME }}
|
||||||
|
tag: ${{ env.GITHUB_REF_NAME }}
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
16
README.md
16
README.md
@ -1,16 +0,0 @@
|
|||||||
# Static Site Generator (SSG)
|
|
||||||
|
|
||||||
## Updating Gradle
|
|
||||||
|
|
||||||
Update the Gradle wrapper via `gradle/wrapper/gradle-wrapper.properties`. Make sure that the tooling-api dependency is
|
|
||||||
updated to the same version in `cli/build.gradle`.
|
|
||||||
|
|
||||||
## Version-bumping
|
|
||||||
|
|
||||||
Update the version of the project in `buildSrc/src/main/groovy/ssg-common.gradle`. Then update the references to the
|
|
||||||
`cli` and `api` projects in `ssg-gradle-plugin/src/main/java/com/jessebrault/ssg/gradle/SsgGradlePlugin.java`.
|
|
||||||
|
|
||||||
## Publishing
|
|
||||||
|
|
||||||
Gradle command `publishAllPublicationsToJbArchiva<Internal|Snapshots>Repository`. Which one of `internal` or `snapshots`
|
|
||||||
appears depends on the current version.
|
|
58
TODO.md
58
TODO.md
@ -1,40 +1,40 @@
|
|||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
Here will be kept all of the various todos for this project, organized by release.
|
Here will be kept all of the various todos for this project, organized by release.
|
||||||
N.b. that v0.3.0 was skipped because of such a fundamental change in the usage of the
|
|
||||||
program with the incorporation of Groowt and Web View Components.
|
|
||||||
|
|
||||||
## 0.6.0
|
## Future
|
||||||
- [ ] Plugin system for build scripts
|
|
||||||
|
|
||||||
## 0.5.0
|
### Add
|
||||||
- [ ] watch/dev mode and server
|
- [ ] Plan out plugin system such that we can create custom providers of texts, data, etc.
|
||||||
- [ ] Reorganize gradle project layout so there is less hunting around for files
|
- [ ] Add `Watchable` interface/trait back; an abstraction over FS watching and other sources (such as a database, etc.).
|
||||||
|
- [ ] Explore `apply(Plugin)` in buildScripts.
|
||||||
|
|
||||||
## 0.4.* Ongoing
|
### Fix
|
||||||
- [ ] Automate test project
|
|
||||||
- [ ] Move as much gradle integration from `cli` project to `api` project
|
|
||||||
- [ ] Think about abstracting the build tool logic, because all we need
|
|
||||||
really is the URLs/Paths for the classes/jars of components and resources
|
|
||||||
- [ ] Document new api and usage.
|
|
||||||
- [ ] Re-incorporate dist plugin in gradle build of cli/api
|
|
||||||
- [ ] Think about how these might be used without a gradle project backing
|
|
||||||
|
|
||||||
## 0.4.3
|
## v0.2.0
|
||||||
- [ ] `Text` component for simply rendering Text objects. Can be used as such:
|
|
||||||
```
|
|
||||||
<Text path='/SomeText.md' />
|
|
||||||
<Text name='SomeText.md' />
|
|
||||||
<Text text={text} />
|
|
||||||
```
|
|
||||||
- [ ] `TextContainer` for accessing all found texts
|
|
||||||
- [ ] `ModelFactory` for creating models, and `TextModelFactory` for creating models from texts.
|
|
||||||
- [ ] `Model` component for rendering a model with either a supplied renderer, or a registered `ModelRenderer`
|
|
||||||
- [ ] `Global` component for rendering globals.
|
|
||||||
- [ ] Automatically inject self PageSpec and path to Pages.
|
|
||||||
|
|
||||||
## 0.4.1
|
### Add
|
||||||
- [x] Update groowt to 0.1.2.
|
- [ ] Write manual.
|
||||||
|
- [x] Remove `lib` module.
|
||||||
|
- [x] Add a way for CLI to choose a build to do, or multiple builds, defaulting to 'default' if it exists.
|
||||||
|
- [ ] Still must work on 'default'-ing.
|
||||||
|
- [x] Write lots of tests for buildscript dsl, etc.
|
||||||
|
- [x] Explore `base` in buildScript dsl.
|
||||||
|
- Get rid of `allBuilds` concept, and replace it with composable/concat-able builds. In the dsl we could have a notion of `abstractBuild` which can be 'extended' (i.e., on the left side of a concat operation) but not actually run (since it doesn't have a name).
|
||||||
|
- `OutputDir` should be concat-able, such that the left is the *base* for the right.
|
||||||
|
- `OutputDirFunctions.concat` should be concat-able as well, such that both are `BiFunction<OutputDir, Build, OutputDir>`, and the output of the left is the input of the right.
|
||||||
|
- Make the delegates as dumb as possible; no more `getResult` methods; make different classes/object handle concat'ing and getting results.
|
||||||
|
- [x] Provide a way to override `ssgBuilds` variables from the cli.
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
- [ ] Update CHANGELOG to reflect the gsp-dsl changes.
|
||||||
|
- [ ] `taskTypes` gone, use class name instead
|
||||||
|
- [ ] introduction of `models`
|
||||||
|
- [x] Change most instances of `Closure<Void>` to `Closure<?>` to help with IDE expectations.
|
||||||
|
- [ ] Fix auto-imports in build script so we don't need to import things.
|
||||||
|
- [ ] Re-introduce input/output concept to tasks, if possible
|
||||||
|
|
||||||
|
## Finished
|
||||||
|
|
||||||
### v0.2.0
|
### v0.2.0
|
||||||
- [x] Investigate imports, including static, in scripts
|
- [x] Investigate imports, including static, in scripts
|
||||||
|
@ -1,58 +1,28 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'ssg-common'
|
id 'ssg.common'
|
||||||
id 'groovy'
|
|
||||||
id 'java-library'
|
|
||||||
id 'java-test-fixtures'
|
|
||||||
id 'maven-publish'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations {
|
|
||||||
testFixturesApi {
|
|
||||||
extendsFrom configurations.testing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api libs.groovy
|
// https://mvnrepository.com/artifact/org.apache.groovy/groovy-templates
|
||||||
api libs.groovy.yaml
|
implementation 'org.apache.groovy:groovy-templates:4.0.12'
|
||||||
|
|
||||||
api libs.groowt.v
|
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
||||||
api libs.groowt.vc
|
implementation 'org.commonmark:commonmark:0.21.0'
|
||||||
api libs.groowt.wvc
|
|
||||||
api libs.di
|
|
||||||
api libs.fp
|
|
||||||
|
|
||||||
compileOnlyApi libs.jetbrains.anontations
|
// https://mvnrepository.com/artifact/org.commonmark/commonmark-ext-yaml-front-matter
|
||||||
|
implementation 'org.commonmark:commonmark-ext-yaml-front-matter:0.21.0'
|
||||||
|
|
||||||
implementation libs.classgraph
|
// https://mvnrepository.com/artifact/org.jsoup/jsoup
|
||||||
implementation libs.commonmark
|
implementation 'org.jsoup:jsoup:1.16.1'
|
||||||
implementation libs.commonmark.frontmatter
|
|
||||||
implementation libs.jsoup
|
|
||||||
|
|
||||||
runtimeOnly libs.groowt.wvcc
|
// https://mvnrepository.com/artifact/org.jgrapht/jgrapht-core
|
||||||
}
|
implementation 'org.jgrapht:jgrapht-core:1.5.2'
|
||||||
|
|
||||||
java {
|
|
||||||
withSourcesJar()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
archivesBaseName = 'ssg-api'
|
archivesBaseName = 'ssg-api'
|
||||||
}
|
}
|
||||||
|
|
||||||
sourcesJar {
|
|
||||||
archiveBaseName = 'ssg-api'
|
|
||||||
}
|
|
||||||
|
|
||||||
publishing {
|
|
||||||
publications {
|
|
||||||
create('ssgApi', MavenPublication) {
|
|
||||||
artifactId = 'api'
|
|
||||||
from components.java
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,106 @@
|
|||||||
|
package com.jessebrault.ssg
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.buildscript.Build
|
||||||
|
import com.jessebrault.ssg.buildscript.BuildScriptConfiguratorFactory
|
||||||
|
import com.jessebrault.ssg.buildscript.BuildScripts
|
||||||
|
import com.jessebrault.ssg.util.Diagnostic
|
||||||
|
import org.jetbrains.annotations.Nullable
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.slf4j.Marker
|
||||||
|
import org.slf4j.MarkerFactory
|
||||||
|
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
final class BuildScriptBasedStaticSiteGenerator implements StaticSiteGenerator {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(BuildScriptBasedStaticSiteGenerator)
|
||||||
|
private static final Marker enter = MarkerFactory.getMarker('enter')
|
||||||
|
private static final Marker exit = MarkerFactory.getMarker('exit')
|
||||||
|
|
||||||
|
private final Collection<BuildScriptConfiguratorFactory> configuratorFactories
|
||||||
|
@Nullable
|
||||||
|
private final File buildScript
|
||||||
|
private final Collection<File> buildSrcDirs
|
||||||
|
private final Map<String, Object> scriptArgs
|
||||||
|
private final Collection<Build> builds = []
|
||||||
|
|
||||||
|
private boolean ranBuildScript = false
|
||||||
|
|
||||||
|
BuildScriptBasedStaticSiteGenerator(
|
||||||
|
Collection<BuildScriptConfiguratorFactory> configuratorFactories = [],
|
||||||
|
@Nullable File buildScript = null,
|
||||||
|
Collection<File> buildSrcDirs = [],
|
||||||
|
Map<String, Object> scriptArgs = [:]
|
||||||
|
) {
|
||||||
|
this.configuratorFactories = configuratorFactories
|
||||||
|
this.buildScript = buildScript
|
||||||
|
this.buildSrcDirs = buildSrcDirs
|
||||||
|
this.scriptArgs = scriptArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runBuildScript() {
|
||||||
|
logger.trace(enter, '')
|
||||||
|
|
||||||
|
if (this.buildScript == null) {
|
||||||
|
logger.info('no specified build script; using defaults')
|
||||||
|
def result = BuildScripts.runBuildScript { base ->
|
||||||
|
this.configuratorFactories.each {
|
||||||
|
it.get().accept(base)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.builds.addAll(result)
|
||||||
|
} else if (this.buildScript.exists() && this.buildScript.isFile()) {
|
||||||
|
logger.info('running buildScript: {}', this.buildScript)
|
||||||
|
def result = BuildScripts.runBuildScript(
|
||||||
|
this.buildScript.name,
|
||||||
|
this.buildScript.parentFile.toURI().toURL(),
|
||||||
|
this.buildSrcDirs.collect { it.toURI().toURL() },
|
||||||
|
[args: this.scriptArgs]
|
||||||
|
) { base ->
|
||||||
|
this.configuratorFactories.each {
|
||||||
|
it.get().accept(base)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.builds.addAll(result)
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("given buildScript ${ this.buildScript } either does not exist or is not a file")
|
||||||
|
}
|
||||||
|
this.ranBuildScript = true
|
||||||
|
|
||||||
|
logger.trace(exit, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean doBuild(String buildName, Consumer<Collection<Diagnostic>> diagnosticsConsumer = { }) {
|
||||||
|
logger.trace(enter, 'buildName: {}, diagnosticsConsumer: {}', buildName, diagnosticsConsumer)
|
||||||
|
|
||||||
|
if (!this.ranBuildScript) {
|
||||||
|
this.runBuildScript()
|
||||||
|
}
|
||||||
|
|
||||||
|
def build = this.builds.find { it.name == buildName }
|
||||||
|
if (!build) {
|
||||||
|
throw new IllegalArgumentException("there is no registered build with name: ${ buildName }")
|
||||||
|
}
|
||||||
|
|
||||||
|
def buildTasksConverter = new SimpleBuildTasksConverter()
|
||||||
|
def successful = true
|
||||||
|
def tasksResult = buildTasksConverter.convert(build)
|
||||||
|
if (tasksResult.hasDiagnostics()) {
|
||||||
|
successful = false
|
||||||
|
diagnosticsConsumer.accept(tasksResult.diagnostics)
|
||||||
|
} else {
|
||||||
|
def tasks = tasksResult.get()
|
||||||
|
def taskDiagnostics = tasks.collectMany { it.execute(tasks) }
|
||||||
|
if (!taskDiagnostics.isEmpty()) {
|
||||||
|
successful = false
|
||||||
|
diagnosticsConsumer.accept(taskDiagnostics)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.trace(exit, 'successful: {}', successful)
|
||||||
|
successful
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
@ -1,322 +0,0 @@
|
|||||||
package com.jessebrault.ssg
|
|
||||||
|
|
||||||
import com.jessebrault.ssg.buildscript.BuildScriptGetter
|
|
||||||
import com.jessebrault.ssg.buildscript.BuildScriptToBuildSpecConverter
|
|
||||||
import com.jessebrault.ssg.buildscript.BuildSpec
|
|
||||||
import com.jessebrault.ssg.buildscript.delegates.BuildDelegate
|
|
||||||
import com.jessebrault.ssg.di.*
|
|
||||||
import com.jessebrault.ssg.page.DefaultWvcPage
|
|
||||||
import com.jessebrault.ssg.page.Page
|
|
||||||
import com.jessebrault.ssg.page.PageFactory
|
|
||||||
import com.jessebrault.ssg.page.PageSpec
|
|
||||||
import com.jessebrault.ssg.text.Text
|
|
||||||
import com.jessebrault.ssg.util.Diagnostic
|
|
||||||
import com.jessebrault.ssg.view.PageView
|
|
||||||
import com.jessebrault.ssg.view.SkipTemplate
|
|
||||||
import com.jessebrault.ssg.view.WvcCompiler
|
|
||||||
import com.jessebrault.ssg.view.WvcPageView
|
|
||||||
import groovy.transform.TupleConstructor
|
|
||||||
import com.jessebrault.di.ObjectFactory
|
|
||||||
import com.jessebrault.di.RegistryObjectFactory
|
|
||||||
import com.jessebrault.fp.option.Option
|
|
||||||
import groowt.view.component.compiler.SimpleComponentTemplateClassFactory
|
|
||||||
import groowt.view.component.factory.ComponentFactories
|
|
||||||
import groowt.view.component.web.DefaultWebViewComponentContext
|
|
||||||
import groowt.view.component.web.WebViewComponent
|
|
||||||
import groowt.view.component.web.WebViewComponentContext
|
|
||||||
import groowt.view.component.web.WebViewComponentScope
|
|
||||||
import io.github.classgraph.ClassGraph
|
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
|
||||||
|
|
||||||
import static com.jessebrault.di.BindingUtil.named
|
|
||||||
import static com.jessebrault.di.BindingUtil.toSingleton
|
|
||||||
|
|
||||||
@TupleConstructor(includeFields = true, defaults = false)
|
|
||||||
class DefaultStaticSiteGenerator implements StaticSiteGenerator {
|
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(DefaultStaticSiteGenerator)
|
|
||||||
|
|
||||||
protected final GroovyClassLoader groovyClassLoader
|
|
||||||
protected final URL[] buildScriptBaseUrls
|
|
||||||
protected final boolean dryRun
|
|
||||||
|
|
||||||
protected Set<Text> getTexts(String buildScriptFqn, BuildSpec buildSpec) {
|
|
||||||
def textConverters = buildSpec.textConverters.get {
|
|
||||||
new SsgException("The textConverters Property in $buildScriptFqn must contain at least an empty Set.")
|
|
||||||
}
|
|
||||||
def textDirs = buildSpec.textsDirs.get {
|
|
||||||
new SsgException("The textDirs Property in $buildScriptFqn must contain at least an empty Set.")
|
|
||||||
}
|
|
||||||
def texts = [] as Set<Text>
|
|
||||||
textDirs.each { textDir ->
|
|
||||||
if (textDir.exists()) {
|
|
||||||
Files.walk(textDir.toPath()).each {
|
|
||||||
def asFile = it.toFile()
|
|
||||||
def lastDot = asFile.name.lastIndexOf('.')
|
|
||||||
if (lastDot != -1) {
|
|
||||||
def extension = asFile.name.substring(lastDot)
|
|
||||||
def converter = textConverters.find {
|
|
||||||
it.handledExtensions.contains(extension)
|
|
||||||
}
|
|
||||||
texts << converter.convert(textDir, asFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
texts
|
|
||||||
}
|
|
||||||
|
|
||||||
protected WvcCompiler getWvcCompiler() {
|
|
||||||
new WvcCompiler(this.groovyClassLoader, new SimpleComponentTemplateClassFactory(this.groovyClassLoader))
|
|
||||||
}
|
|
||||||
|
|
||||||
protected WebViewComponentContext makeContext(
|
|
||||||
Set<Class<? extends WebViewComponent>> allWvc,
|
|
||||||
RegistryObjectFactory objectFactory,
|
|
||||||
WvcPageView pageView
|
|
||||||
) {
|
|
||||||
new DefaultWebViewComponentContext().tap {
|
|
||||||
configureRootScope(WebViewComponentScope) {
|
|
||||||
// custom components
|
|
||||||
allWvc.each { wvcClass ->
|
|
||||||
//noinspection GroovyAssignabilityCheck
|
|
||||||
add(wvcClass, ComponentFactories.ofClosureClassType(wvcClass) { Map attr, Object[] args ->
|
|
||||||
WebViewComponent component
|
|
||||||
if (!attr.isEmpty() && args.length > 0) {
|
|
||||||
component = objectFactory.createInstance(wvcClass, attr, *args)
|
|
||||||
} else if (!attr.isEmpty()) {
|
|
||||||
component = objectFactory.createInstance(wvcClass, attr)
|
|
||||||
} else if (args.length > 0) {
|
|
||||||
component = objectFactory.createInstance(wvcClass, *args)
|
|
||||||
} else {
|
|
||||||
component = objectFactory.createInstance(wvcClass)
|
|
||||||
}
|
|
||||||
component.context = pageView.context
|
|
||||||
if (component.componentTemplate == null
|
|
||||||
&& !wvcClass.isAnnotationPresent(SkipTemplate)) {
|
|
||||||
def compileResult = objectFactory.get(WvcCompiler).compileTemplate(
|
|
||||||
wvcClass,
|
|
||||||
wvcClass.simpleName + 'Template.wvc'
|
|
||||||
)
|
|
||||||
if (compileResult.isRight()) {
|
|
||||||
component.componentTemplate = compileResult.getRight()
|
|
||||||
} else {
|
|
||||||
def left = compileResult.getLeft()
|
|
||||||
throw new RuntimeException(left.message, left.exception)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return component
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Option<Diagnostic> handlePage(
|
|
||||||
String buildScriptFqn,
|
|
||||||
Page page,
|
|
||||||
BuildSpec buildSpec,
|
|
||||||
Set<Class<? extends WebViewComponent>> allWvc,
|
|
||||||
ObjectFactory objectFactory
|
|
||||||
) {
|
|
||||||
// instantiate PageView
|
|
||||||
def pageViewResult = page.createView()
|
|
||||||
if (pageViewResult.isLeft()) {
|
|
||||||
return Option.lift(pageViewResult.getLeft())
|
|
||||||
}
|
|
||||||
PageView pageView = pageViewResult.getRight()
|
|
||||||
|
|
||||||
// Prepare for rendering
|
|
||||||
pageView.pageTitle = page.name
|
|
||||||
pageView.url = buildSpec.baseUrl.get() + page.path
|
|
||||||
|
|
||||||
if (pageView instanceof WvcPageView) {
|
|
||||||
pageView.context = this.makeContext(allWvc, objectFactory, pageView)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render the page
|
|
||||||
def sw = new StringWriter()
|
|
||||||
try {
|
|
||||||
pageView.renderTo(sw)
|
|
||||||
} catch (Exception exception) {
|
|
||||||
return Option.lift(new Diagnostic(
|
|
||||||
"There was an exception while rendering $page.name as $pageView.class.name",
|
|
||||||
exception
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output the page if not dryRun
|
|
||||||
if (!this.dryRun) {
|
|
||||||
def outputDir = buildSpec.outputDir.get {
|
|
||||||
new SsgException("The outputDir Property in $buildScriptFqn must be set.")
|
|
||||||
}
|
|
||||||
outputDir.mkdirs()
|
|
||||||
|
|
||||||
def splitPathParts = page.path.split('/')
|
|
||||||
def pathParts = page.path.endsWith('/')
|
|
||||||
? splitPathParts + 'index'
|
|
||||||
: splitPathParts
|
|
||||||
|
|
||||||
def path = Path.of(pathParts[0], pathParts.drop(1))
|
|
||||||
|
|
||||||
def outputFile = new File(
|
|
||||||
outputDir,
|
|
||||||
path.toString() + page.fileExtension
|
|
||||||
)
|
|
||||||
outputFile.parentFile.mkdirs()
|
|
||||||
try {
|
|
||||||
outputFile.write(sw.toString())
|
|
||||||
} catch (Exception exception) {
|
|
||||||
return Option.lift(new Diagnostic(
|
|
||||||
"There was an exception while writing $page.name to $outputFile",
|
|
||||||
exception
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Option.empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
Collection<Diagnostic> doBuild(
|
|
||||||
File projectDir,
|
|
||||||
String buildName,
|
|
||||||
String buildScriptFqn,
|
|
||||||
Map<String, String> buildScriptCliArgs
|
|
||||||
) {
|
|
||||||
def wvcCompiler = this.getWvcCompiler()
|
|
||||||
|
|
||||||
// run build script(s) and get buildSpec
|
|
||||||
def buildScriptGetter = new BuildScriptGetter(
|
|
||||||
this.groovyClassLoader,
|
|
||||||
this.buildScriptBaseUrls,
|
|
||||||
buildScriptCliArgs,
|
|
||||||
projectDir
|
|
||||||
)
|
|
||||||
def buildScriptToBuildSpecConverter = new BuildScriptToBuildSpecConverter(
|
|
||||||
buildScriptGetter, { String name -> BuildDelegate.withDefaults(name, projectDir) }
|
|
||||||
)
|
|
||||||
def buildSpec = buildScriptToBuildSpecConverter.convert(buildScriptFqn)
|
|
||||||
|
|
||||||
// prepare objectFactory
|
|
||||||
def objectFactoryBuilder = buildSpec.objectFactoryBuilder.get {
|
|
||||||
new SsgException(
|
|
||||||
"the objectFactoryBuilder Property in $buildScriptFqn " +
|
|
||||||
"must contain a RegistryObjectFactory.Builder instance."
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// configure objectFactory for Page/PageFactory instantiation
|
|
||||||
objectFactoryBuilder.configureRegistry {
|
|
||||||
// extensions
|
|
||||||
addExtension(new TextsExtension().tap {
|
|
||||||
allTexts.addAll(this.getTexts(buildScriptFqn, buildSpec))
|
|
||||||
})
|
|
||||||
addExtension(new ModelsExtension().tap {
|
|
||||||
allModels.addAll(buildSpec.models.get {
|
|
||||||
new SsgException("The models Property in $buildScriptFqn must contain at least an empty Set.")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
addExtension(new GlobalsExtension().tap {
|
|
||||||
globals.putAll(buildSpec.globals.get {
|
|
||||||
new SsgException("The globals Property in $buildScriptFqn must contain at least an empty Map.")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// bindings
|
|
||||||
bind(named('buildName', String), toSingleton(buildSpec.name))
|
|
||||||
bind(named('siteName', String), toSingleton(buildSpec.siteName.get {
|
|
||||||
new SsgException("The siteName Property in $buildScriptFqn must be set.")
|
|
||||||
}))
|
|
||||||
bind(named('baseUrl', String), toSingleton(buildSpec.baseUrl.get {
|
|
||||||
new SsgException("The baseUrl Property in $buildScriptFqn must be set.")
|
|
||||||
}))
|
|
||||||
bind(WvcCompiler, toSingleton(wvcCompiler))
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the objectFactory
|
|
||||||
def objectFactory = objectFactoryBuilder.build()
|
|
||||||
objectFactory.configureRegistry {
|
|
||||||
bind(RegistryObjectFactory, toSingleton(objectFactory))
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepare for basePackages scan for Pages and PageFactories
|
|
||||||
def basePackages = buildSpec.basePackages.get {
|
|
||||||
new SsgException("The basePackages Property in $buildScriptFqn must contain at least an empty Set.")
|
|
||||||
}
|
|
||||||
def classgraph = new ClassGraph()
|
|
||||||
.enableAnnotationInfo()
|
|
||||||
.addClassLoader(this.groovyClassLoader)
|
|
||||||
basePackages.each { classgraph.acceptPackages(it) }
|
|
||||||
|
|
||||||
def pages = [] as Set<Page>
|
|
||||||
def allWvc = [] as Set<Class<? extends WebViewComponent>>
|
|
||||||
|
|
||||||
try (def scanResult = classgraph.scan()) {
|
|
||||||
// single pages
|
|
||||||
def pageViewInfoList = scanResult.getClassesImplementing(PageView)
|
|
||||||
pageViewInfoList.each { pageViewInfo ->
|
|
||||||
def pageSpecInfo = pageViewInfo.getAnnotationInfo(PageSpec)
|
|
||||||
if (pageSpecInfo != null) {
|
|
||||||
def pageSpec = (PageSpec) pageSpecInfo.loadClassAndInstantiate()
|
|
||||||
pages << new DefaultWvcPage(
|
|
||||||
name: pageSpec.name(),
|
|
||||||
path: pageSpec.path(),
|
|
||||||
fileExtension: pageSpec.fileExtension(),
|
|
||||||
viewType: (Class<? extends PageView>) pageViewInfo.loadClass(),
|
|
||||||
templateResource: !pageSpec.templateResource().empty
|
|
||||||
? pageSpec.templateResource()
|
|
||||||
: pageViewInfo.simpleName + 'Template.wvc',
|
|
||||||
objectFactory: objectFactory,
|
|
||||||
wvcCompiler: wvcCompiler
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// page factories
|
|
||||||
def pageFactoryInfoList = scanResult.getClassesImplementing(PageFactory)
|
|
||||||
def pageFactoryTypes = pageFactoryInfoList.collect() { pageFactoryInfo ->
|
|
||||||
(pageFactoryInfo.loadClass() as Class<? extends PageFactory>)
|
|
||||||
}
|
|
||||||
|
|
||||||
// instantiate page factory and create the pages
|
|
||||||
pageFactoryTypes.each { pageFactoryType ->
|
|
||||||
def pageFactory = objectFactory.createInstance(pageFactoryType)
|
|
||||||
def created = pageFactory.create()
|
|
||||||
pages.addAll(created)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get all web view components
|
|
||||||
def wvcInfoList = scanResult.getClassesImplementing(WebViewComponent)
|
|
||||||
wvcInfoList.each {
|
|
||||||
allWvc << it.loadClass(WebViewComponent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure objectFactory for PageView instantiation with the Pages we found/created
|
|
||||||
objectFactory.configureRegistry {
|
|
||||||
// extensions
|
|
||||||
addExtension(new PagesExtension().tap {
|
|
||||||
allPages.addAll(pages)
|
|
||||||
})
|
|
||||||
addExtension(new SelfPageExtension())
|
|
||||||
}
|
|
||||||
|
|
||||||
def diagnostics = [] as Collection<Diagnostic>
|
|
||||||
|
|
||||||
pages.each {
|
|
||||||
def result = this.handlePage(buildScriptFqn, it, buildSpec, allWvc, objectFactory)
|
|
||||||
if (result.isPresent()) {
|
|
||||||
diagnostics << result.get()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// return diagnostics
|
|
||||||
diagnostics
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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 build) {
|
||||||
|
def taskSpec = new TaskSpec(
|
||||||
|
build.name,
|
||||||
|
build.outputDirFunction.apply(build).asFile(),
|
||||||
|
build.siteSpec,
|
||||||
|
build.globals
|
||||||
|
)
|
||||||
|
Collection<Task> tasks = []
|
||||||
|
Collection<Diagnostic> diagnostics = []
|
||||||
|
|
||||||
|
build.taskFactorySpecs.each {
|
||||||
|
def factory = it.supplier.get()
|
||||||
|
it.configurators.each { it.accept(factory) }
|
||||||
|
def result = factory.getTasks(taskSpec)
|
||||||
|
diagnostics.addAll(result.diagnostics)
|
||||||
|
tasks.addAll(result.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
Result.of(diagnostics, tasks)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
39
api/src/main/groovy/com/jessebrault/ssg/SiteSpec.groovy
Normal file
39
api/src/main/groovy/com/jessebrault/ssg/SiteSpec.groovy
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package com.jessebrault.ssg
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.util.Monoid
|
||||||
|
import com.jessebrault.ssg.util.Monoids
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@TupleConstructor(defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class SiteSpec {
|
||||||
|
|
||||||
|
static final Monoid<SiteSpec> DEFAULT_MONOID = Monoids.of(getBlank(), SiteSpec::concat)
|
||||||
|
|
||||||
|
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 })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
package com.jessebrault.ssg
|
|
||||||
|
|
||||||
class SsgException extends RuntimeException {
|
|
||||||
|
|
||||||
SsgException(String message) {
|
|
||||||
super(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
SsgException(String message, Throwable cause) {
|
|
||||||
super(message, cause)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -2,11 +2,8 @@ package com.jessebrault.ssg
|
|||||||
|
|
||||||
import com.jessebrault.ssg.util.Diagnostic
|
import com.jessebrault.ssg.util.Diagnostic
|
||||||
|
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
interface StaticSiteGenerator {
|
interface StaticSiteGenerator {
|
||||||
Collection<Diagnostic> doBuild(
|
boolean doBuild(String buildName, Consumer<Collection<Diagnostic>> diagnosticsConsumer)
|
||||||
File projectDir,
|
|
||||||
String buildName,
|
|
||||||
String buildScriptFqn,
|
|
||||||
Map<String, String> buildScriptCliArgs
|
|
||||||
)
|
|
||||||
}
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.SiteSpec
|
||||||
|
import com.jessebrault.ssg.task.TaskFactory
|
||||||
|
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 {
|
||||||
|
|
||||||
|
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<TaskFactory>> ?: []
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
static Build concat(Build b0, Build b1) {
|
||||||
|
new Build(
|
||||||
|
b0.name.blank ? b1.name : b0.name,
|
||||||
|
OutputDirFunctions.concat(b0.outputDirFunction, b1.outputDirFunction),
|
||||||
|
SiteSpec.concat(b0.siteSpec, b1.siteSpec),
|
||||||
|
b0.globals + b1.globals,
|
||||||
|
b0.taskFactorySpecs + b1.taskFactorySpecs
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
final String name
|
||||||
|
final Function<Build, OutputDir> outputDirFunction
|
||||||
|
final SiteSpec siteSpec
|
||||||
|
final Map<String, Object> globals
|
||||||
|
final Collection<TaskFactorySpec<TaskFactory>> taskFactorySpecs
|
||||||
|
|
||||||
|
Build plus(Build other) {
|
||||||
|
concat(this, other)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript
|
||||||
|
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.PackageScope
|
||||||
|
|
||||||
|
@PackageScope
|
||||||
|
@NullCheck
|
||||||
|
final class BuildExtension {
|
||||||
|
|
||||||
|
static BuildExtension getEmpty() {
|
||||||
|
new BuildExtension()
|
||||||
|
}
|
||||||
|
|
||||||
|
static BuildExtension get(String buildName) {
|
||||||
|
new BuildExtension(buildName)
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String buildName
|
||||||
|
|
||||||
|
private BuildExtension(String buildName) {
|
||||||
|
this.buildName = buildName
|
||||||
|
}
|
||||||
|
|
||||||
|
private BuildExtension() {
|
||||||
|
this.buildName = null
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isPresent() {
|
||||||
|
this.buildName != null
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isEmpty() {
|
||||||
|
!this.present
|
||||||
|
}
|
||||||
|
|
||||||
|
String getBuildName() {
|
||||||
|
Objects.requireNonNull(this.buildName)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
this.present ? "BuildExtension(extending: ${ this.buildName })" : "BuildExtension(empty)"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
int hashCode() {
|
||||||
|
Objects.hash(this.buildName)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean equals(Object obj) {
|
||||||
|
obj.is(this)
|
||||||
|
|| (obj instanceof BuildExtension && obj.present && obj.buildName == this.buildName)
|
||||||
|
|| (obj instanceof BuildExtension && !obj.present && !this.present)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript
|
||||||
|
|
||||||
|
import groovy.transform.PackageScope
|
||||||
|
import org.jgrapht.Graph
|
||||||
|
import org.jgrapht.graph.DefaultEdge
|
||||||
|
import org.jgrapht.graph.DirectedAcyclicGraph
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.slf4j.Marker
|
||||||
|
import org.slf4j.MarkerFactory
|
||||||
|
|
||||||
|
@PackageScope
|
||||||
|
final class BuildGraphUtil {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(BuildGraphUtil)
|
||||||
|
private static final Marker enter = MarkerFactory.getMarker('ENTER')
|
||||||
|
private static final Marker exit = MarkerFactory.getMarker('EXIT')
|
||||||
|
|
||||||
|
private static void addParentEdges(
|
||||||
|
Graph<BuildSpec, DefaultEdge> graph,
|
||||||
|
BuildSpec spec,
|
||||||
|
Collection<BuildSpec> allBuildSpecs
|
||||||
|
) {
|
||||||
|
logger.trace(enter, 'graph: {}, spec: {}', graph, spec)
|
||||||
|
if (!graph.containsVertex(spec)) {
|
||||||
|
throw new IllegalStateException("given spec is not in the graph")
|
||||||
|
}
|
||||||
|
if (spec.extending.present) {
|
||||||
|
def parent = allBuildSpecs.find { it.name == spec.extending.buildName }
|
||||||
|
if (parent == null) {
|
||||||
|
throw new IllegalStateException("no such parent/extends from build: ${ spec.extending.buildName }")
|
||||||
|
}
|
||||||
|
if (!graph.containsVertex(parent)) {
|
||||||
|
graph.addVertex(parent)
|
||||||
|
}
|
||||||
|
graph.addEdge(parent, spec)
|
||||||
|
addParentEdges(graph, parent, allBuildSpecs)
|
||||||
|
}
|
||||||
|
logger.trace(exit, '')
|
||||||
|
}
|
||||||
|
|
||||||
|
static Graph<BuildSpec, DefaultEdge> getDependencyGraph(Collection<BuildSpec> buildSpecs) {
|
||||||
|
logger.trace(enter, '')
|
||||||
|
final Graph<BuildSpec, DefaultEdge> graph = new DirectedAcyclicGraph<>(DefaultEdge)
|
||||||
|
buildSpecs.each {
|
||||||
|
if (!graph.containsVertex(it)) {
|
||||||
|
graph.addVertex(it)
|
||||||
|
}
|
||||||
|
addParentEdges(graph, it, buildSpecs)
|
||||||
|
}
|
||||||
|
logger.trace(exit, 'graph: {}', graph)
|
||||||
|
graph
|
||||||
|
}
|
||||||
|
|
||||||
|
static Collection<BuildSpec> getAncestors(BuildSpec child, Graph<BuildSpec, DefaultEdge> graph) {
|
||||||
|
logger.trace(enter, 'child: {}, graph: {}', child, graph)
|
||||||
|
if (child.extending.isEmpty()) {
|
||||||
|
def r = [] as Collection<BuildSpec>
|
||||||
|
logger.trace(exit, 'r: {}', r)
|
||||||
|
r
|
||||||
|
} else {
|
||||||
|
// use incoming to get edges pointing to child
|
||||||
|
def incomingEdges = graph.incomingEdgesOf(child)
|
||||||
|
if (incomingEdges.size() == 0) {
|
||||||
|
throw new IllegalArgumentException("child does not have an edge to its parent")
|
||||||
|
}
|
||||||
|
if (incomingEdges.size() > 1) {
|
||||||
|
throw new IllegalArgumentException("child has more than one parent")
|
||||||
|
}
|
||||||
|
def parent = graph.getEdgeSource(incomingEdges[0])
|
||||||
|
def r = getAncestors(parent, graph) + [parent] // needs to be 'oldest' -> 'youngest'
|
||||||
|
logger.trace(exit, 'r: {}', r)
|
||||||
|
r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BuildGraphUtil() {}
|
||||||
|
|
||||||
|
}
|
@ -1,8 +1,7 @@
|
|||||||
package com.jessebrault.ssg.buildscript
|
package com.jessebrault.ssg.buildscript
|
||||||
|
|
||||||
import com.jessebrault.ssg.buildscript.delegates.BuildDelegate
|
import com.jessebrault.ssg.buildscript.delegates.BuildDelegate
|
||||||
import org.jetbrains.annotations.ApiStatus
|
import groovy.transform.PackageScope
|
||||||
import org.jetbrains.annotations.Nullable
|
|
||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import org.slf4j.Marker
|
import org.slf4j.Marker
|
||||||
@ -10,67 +9,55 @@ import org.slf4j.MarkerFactory
|
|||||||
|
|
||||||
import static java.util.Objects.requireNonNull
|
import static java.util.Objects.requireNonNull
|
||||||
|
|
||||||
@SuppressWarnings('unused')
|
|
||||||
abstract class BuildScriptBase extends Script {
|
abstract class BuildScriptBase extends Script {
|
||||||
|
|
||||||
/* --- Logging --- */
|
protected static final Logger logger = LoggerFactory.getLogger(BuildScriptBase)
|
||||||
|
protected static final Marker enter = MarkerFactory.getMarker('ENTER')
|
||||||
|
protected static final Marker exit = MarkerFactory.getMarker('EXIT')
|
||||||
|
|
||||||
static final Logger logger = LoggerFactory.getLogger(BuildScriptBase)
|
protected final Collection<BuildSpec> buildSpecs = []
|
||||||
static final Marker enter = MarkerFactory.getMarker('ENTER')
|
|
||||||
static final Marker exit = MarkerFactory.getMarker('EXIT')
|
|
||||||
|
|
||||||
/* --- build script proper --- */
|
/**
|
||||||
|
* args keys: name (required), extending (optional)
|
||||||
private String extending
|
*
|
||||||
private Closure buildClosure = { }
|
* @param args
|
||||||
private File projectRoot
|
* @param buildClosure
|
||||||
private String buildName
|
*/
|
||||||
|
void abstractBuild(
|
||||||
/* --- Instance DSL helpers --- */
|
Map<String, Object> args,
|
||||||
|
@DelegatesTo(value = BuildDelegate, strategy = Closure.DELEGATE_FIRST)
|
||||||
File file(String name) {
|
Closure<?> buildClosure
|
||||||
new File(this.projectRoot, name)
|
) {
|
||||||
|
this.buildSpecs << new BuildSpec(
|
||||||
|
requireNonNull(args.name as String),
|
||||||
|
true,
|
||||||
|
args.extending != null ? BuildExtension.get(args.extending as String) : BuildExtension.getEmpty(),
|
||||||
|
buildClosure
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- DSL --- */
|
/**
|
||||||
|
* args keys: name (required), extending (optional)
|
||||||
void build(@Nullable String extending, @DelegatesTo(value = BuildDelegate) Closure buildClosure) {
|
*
|
||||||
this.extending = extending
|
* @param args
|
||||||
this.buildClosure = buildClosure
|
* @param buildClosure
|
||||||
|
*/
|
||||||
|
void build(
|
||||||
|
Map<String, Object> args,
|
||||||
|
@DelegatesTo(value = BuildDelegate, strategy = Closure.DELEGATE_FIRST)
|
||||||
|
Closure<?> buildClosure
|
||||||
|
) {
|
||||||
|
this.buildSpecs << new BuildSpec(
|
||||||
|
requireNonNull(args.name as String),
|
||||||
|
false,
|
||||||
|
args.extending != null ? BuildExtension.get(args.extending as String) : BuildExtension.getEmpty(),
|
||||||
|
buildClosure
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
void build(@DelegatesTo(value = BuildDelegate) Closure buildClosure) {
|
@PackageScope
|
||||||
this.extending = null
|
Collection<BuildSpec> getBuildSpecs() {
|
||||||
this.buildClosure = buildClosure
|
this.buildSpecs
|
||||||
}
|
|
||||||
|
|
||||||
File getProjectRoot() {
|
|
||||||
requireNonNull(this.projectRoot)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiStatus.Internal
|
|
||||||
void setProjectRoot(File projectRoot) {
|
|
||||||
this.projectRoot = requireNonNull(projectRoot)
|
|
||||||
}
|
|
||||||
|
|
||||||
String getBuildName() {
|
|
||||||
return buildName
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiStatus.Internal
|
|
||||||
void setBuildName(String buildName) {
|
|
||||||
this.buildName = buildName
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiStatus.Internal
|
|
||||||
@Nullable
|
|
||||||
String getExtending() {
|
|
||||||
this.extending
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiStatus.Internal
|
|
||||||
Closure getBuildClosure() {
|
|
||||||
this.buildClosure
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript
|
||||||
|
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
interface BuildScriptConfiguratorFactory {
|
||||||
|
Consumer<BuildScriptBase> get()
|
||||||
|
}
|
@ -1,30 +0,0 @@
|
|||||||
package com.jessebrault.ssg.buildscript
|
|
||||||
|
|
||||||
import groovy.transform.NullCheck
|
|
||||||
import groovy.transform.TupleConstructor
|
|
||||||
import org.codehaus.groovy.control.CompilerConfiguration
|
|
||||||
|
|
||||||
@NullCheck(includeGenerated = true)
|
|
||||||
@TupleConstructor(includeFields = true, defaults = false)
|
|
||||||
final class BuildScriptGetter {
|
|
||||||
|
|
||||||
private final GroovyClassLoader groovyClassLoader
|
|
||||||
private final URL[] scriptBaseUrls
|
|
||||||
private final Map<String, String> scriptCliArgs
|
|
||||||
private final File projectDir
|
|
||||||
|
|
||||||
BuildScriptBase getAndRunBuildScript(String fqn) {
|
|
||||||
def gcl = new GroovyClassLoader(this.groovyClassLoader, new CompilerConfiguration().tap {
|
|
||||||
it.scriptBaseClass = BuildScriptBase.name
|
|
||||||
})
|
|
||||||
this.scriptBaseUrls.each { gcl.addURL(it) }
|
|
||||||
def scriptClass = gcl.loadClass(fqn, true, true) as Class<BuildScriptBase>
|
|
||||||
def buildScript = scriptClass.getConstructor().newInstance()
|
|
||||||
buildScript.binding = new Binding(this.scriptCliArgs)
|
|
||||||
buildScript.projectRoot = projectDir
|
|
||||||
buildScript.buildName = fqn
|
|
||||||
buildScript.run()
|
|
||||||
buildScript
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
package com.jessebrault.ssg.buildscript
|
|
||||||
|
|
||||||
import com.jessebrault.ssg.buildscript.delegates.BuildDelegate
|
|
||||||
import groovy.transform.NullCheck
|
|
||||||
import groovy.transform.TupleConstructor
|
|
||||||
|
|
||||||
import java.util.function.Function
|
|
||||||
import java.util.function.Supplier
|
|
||||||
|
|
||||||
@NullCheck
|
|
||||||
@TupleConstructor(includeFields = true)
|
|
||||||
class BuildScriptToBuildSpecConverter {
|
|
||||||
|
|
||||||
private final BuildScriptGetter buildScriptGetter
|
|
||||||
private final Function<String, BuildDelegate> buildDelegateFactory
|
|
||||||
|
|
||||||
protected BuildSpec getFromDelegate(String name, BuildDelegate delegate) {
|
|
||||||
new BuildSpec(
|
|
||||||
name: name,
|
|
||||||
basePackages: delegate.basePackages,
|
|
||||||
siteName: delegate.siteName,
|
|
||||||
baseUrl: delegate.baseUrl,
|
|
||||||
outputDir: delegate.outputDir,
|
|
||||||
globals: delegate.globals,
|
|
||||||
models: delegate.models,
|
|
||||||
textsDirs: delegate.textsDirs,
|
|
||||||
textConverters: delegate.textConverters,
|
|
||||||
objectFactoryBuilder: delegate.objectFactoryBuilder
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected BuildSpec doConvert(String buildScriptFqn, BuildScriptBase buildScript) {
|
|
||||||
final Deque<BuildScriptBase> buildHierarchy = new LinkedList<>()
|
|
||||||
buildHierarchy.push(buildScript)
|
|
||||||
String extending = buildScript.extending
|
|
||||||
while (extending != null) {
|
|
||||||
def from = this.buildScriptGetter.getAndRunBuildScript(extending)
|
|
||||||
buildHierarchy.push(from)
|
|
||||||
extending = from.extending
|
|
||||||
}
|
|
||||||
|
|
||||||
def delegate = this.buildDelegateFactory.apply(buildScriptFqn)
|
|
||||||
while (!buildHierarchy.isEmpty()) {
|
|
||||||
def currentScript = buildHierarchy.pop()
|
|
||||||
currentScript.buildClosure.delegate = delegate
|
|
||||||
currentScript.buildClosure()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.getFromDelegate(buildScriptFqn, delegate)
|
|
||||||
}
|
|
||||||
|
|
||||||
BuildSpec convert(String buildScriptFqn) {
|
|
||||||
def start = this.buildScriptGetter.getAndRunBuildScript(buildScriptFqn)
|
|
||||||
this.doConvert(buildScriptFqn, start)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,79 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript
|
||||||
|
|
||||||
|
import groovy.transform.stc.ClosureParams
|
||||||
|
import groovy.transform.stc.SimpleType
|
||||||
|
import org.codehaus.groovy.control.CompilerConfiguration
|
||||||
|
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
final class BuildScripts {
|
||||||
|
|
||||||
|
private static Collection<Build> runBase(BuildScriptBase base) {
|
||||||
|
base.run()
|
||||||
|
BuildSpecUtil.getBuilds(base.getBuildSpecs())
|
||||||
|
}
|
||||||
|
|
||||||
|
static Collection<Build> runBuildScript(
|
||||||
|
@DelegatesTo(value = BuildScriptBase, strategy = Closure.DELEGATE_FIRST)
|
||||||
|
@ClosureParams(value = SimpleType, options = 'com.jessebrault.ssg.buildscript.BuildScriptBase')
|
||||||
|
Closure<?> scriptBody
|
||||||
|
) {
|
||||||
|
def base = new BuildScriptBase() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Object run() {
|
||||||
|
scriptBody.delegate = this
|
||||||
|
scriptBody.resolveStrategy = Closure.DELEGATE_FIRST
|
||||||
|
scriptBody.call(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
runBase(base)
|
||||||
|
}
|
||||||
|
|
||||||
|
static Collection<Build> runBuildScript(
|
||||||
|
String scriptName,
|
||||||
|
URL scriptBaseDirUrl,
|
||||||
|
Collection<URL> otherUrls,
|
||||||
|
Map<String, Object> binding,
|
||||||
|
Consumer<BuildScriptBase> configureBuildScript
|
||||||
|
) {
|
||||||
|
def engine = new GroovyScriptEngine([scriptBaseDirUrl, *otherUrls] as URL[])
|
||||||
|
|
||||||
|
engine.config = new CompilerConfiguration().tap {
|
||||||
|
scriptBaseClass = 'com.jessebrault.ssg.buildscript.BuildScriptBase'
|
||||||
|
}
|
||||||
|
|
||||||
|
def base = engine.createScript(scriptName, new Binding(binding))
|
||||||
|
assert base instanceof BuildScriptBase
|
||||||
|
configureBuildScript.accept(base)
|
||||||
|
runBase(base)
|
||||||
|
}
|
||||||
|
|
||||||
|
static Collection<Build> runBuildScript(
|
||||||
|
String scriptName,
|
||||||
|
URL scriptBaseDirUrl,
|
||||||
|
Collection<URL> otherUrls,
|
||||||
|
Map<String, Object> binding
|
||||||
|
) {
|
||||||
|
runBuildScript(scriptName, scriptBaseDirUrl, otherUrls, binding) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
static Collection<Build> runBuildScript(
|
||||||
|
String scriptName,
|
||||||
|
URL scriptBaseDirUrl,
|
||||||
|
Collection<URL> otherUrls
|
||||||
|
) {
|
||||||
|
runBuildScript(scriptName, scriptBaseDirUrl, otherUrls, [:]) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
static Collection<Build> runBuildScript(
|
||||||
|
String scriptName,
|
||||||
|
URL scriptBaseDirUrl
|
||||||
|
) {
|
||||||
|
runBuildScript(scriptName, scriptBaseDirUrl, [], [:]) { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private BuildScripts() {}
|
||||||
|
|
||||||
|
}
|
@ -1,48 +1,50 @@
|
|||||||
package com.jessebrault.ssg.buildscript
|
package com.jessebrault.ssg.buildscript
|
||||||
|
|
||||||
import com.jessebrault.ssg.model.Model
|
import com.jessebrault.ssg.buildscript.delegates.BuildDelegate
|
||||||
import com.jessebrault.ssg.text.TextConverter
|
|
||||||
import groovy.transform.EqualsAndHashCode
|
import groovy.transform.EqualsAndHashCode
|
||||||
import groovy.transform.NullCheck
|
import groovy.transform.NullCheck
|
||||||
import com.jessebrault.di.RegistryObjectFactory
|
import groovy.transform.PackageScope
|
||||||
import com.jessebrault.fp.provider.Provider
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
import static com.jessebrault.ssg.util.ObjectUtil.requireProvider
|
@PackageScope
|
||||||
import static com.jessebrault.ssg.util.ObjectUtil.requireString
|
@NullCheck()
|
||||||
|
@EqualsAndHashCode(excludes = 'buildClosure')
|
||||||
@NullCheck(includeGenerated = true)
|
|
||||||
@EqualsAndHashCode
|
|
||||||
final class BuildSpec {
|
final class BuildSpec {
|
||||||
|
|
||||||
final String name
|
static BuildSpec getEmpty() {
|
||||||
final Provider<Set<String>> basePackages
|
new BuildSpec('', false, BuildExtension.getEmpty(), { })
|
||||||
final Provider<String> siteName
|
}
|
||||||
final Provider<String> baseUrl
|
|
||||||
final Provider<File> outputDir
|
|
||||||
final Provider<Map<String, Object>> globals
|
|
||||||
final Provider<Set<Model>> models
|
|
||||||
final Provider<Set<File>> textsDirs
|
|
||||||
final Provider<Set<TextConverter>> textConverters
|
|
||||||
final Provider<RegistryObjectFactory.Builder> objectFactoryBuilder
|
|
||||||
|
|
||||||
@SuppressWarnings('GroovyAssignabilityCheck')
|
static BuildSpec get(Map<String, Object> args) {
|
||||||
BuildSpec(Map args) {
|
new BuildSpec(
|
||||||
this.name = requireString(args.name)
|
args.name as String ?: '',
|
||||||
this.basePackages = requireProvider(args.basePackages)
|
args.isAbstract as boolean ?: false,
|
||||||
this.siteName = requireProvider(args.siteName)
|
args.extending as BuildExtension ?: BuildExtension.getEmpty(),
|
||||||
this.baseUrl = requireProvider(args.baseUrl)
|
args.buildClosure as Closure<?> ?: { }
|
||||||
this.outputDir = requireProvider(args.outputDir)
|
)
|
||||||
this.globals = requireProvider(args.globals)
|
}
|
||||||
this.models = requireProvider(args.models)
|
|
||||||
this.textsDirs = requireProvider(args.textsDirs)
|
final String name
|
||||||
this.textConverters = requireProvider(args.textConverters)
|
final boolean isAbstract
|
||||||
this.objectFactoryBuilder = requireProvider(args.objectFactoryBuilder)
|
final BuildExtension extending
|
||||||
|
final Closure<?> buildClosure
|
||||||
|
|
||||||
|
BuildSpec(
|
||||||
|
String name,
|
||||||
|
boolean isAbstract,
|
||||||
|
BuildExtension extending,
|
||||||
|
@DelegatesTo(value = BuildDelegate, strategy = Closure.DELEGATE_FIRST)
|
||||||
|
Closure<?> buildClosure
|
||||||
|
) {
|
||||||
|
this.name = name
|
||||||
|
this.isAbstract = isAbstract
|
||||||
|
this.extending = extending
|
||||||
|
this.buildClosure = buildClosure
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
String toString() {
|
String toString() {
|
||||||
"Build(name: ${this.name}, basePackages: $basePackages, siteName: $siteName, " +
|
"BuildSpec(name: ${ this.name }, isAbstract: ${ this.isAbstract }, extending: ${ this.extending })"
|
||||||
"baseUrl: $baseUrl, outputDir: $outputDir, textsDirs: $textsDirs)"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,105 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.SiteSpec
|
||||||
|
import com.jessebrault.ssg.buildscript.delegates.BuildDelegate
|
||||||
|
import com.jessebrault.ssg.task.TaskFactory
|
||||||
|
import com.jessebrault.ssg.task.TaskFactorySpec
|
||||||
|
import com.jessebrault.ssg.util.Monoid
|
||||||
|
import com.jessebrault.ssg.util.Monoids
|
||||||
|
import com.jessebrault.ssg.util.Zero
|
||||||
|
import org.jgrapht.traverse.DepthFirstIterator
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.slf4j.Marker
|
||||||
|
import org.slf4j.MarkerFactory
|
||||||
|
|
||||||
|
import java.util.function.BiFunction
|
||||||
|
|
||||||
|
final class BuildSpecUtil {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(BuildSpecUtil)
|
||||||
|
private static final Marker enter = MarkerFactory.getMarker('ENTER')
|
||||||
|
private static final Marker exit = MarkerFactory.getMarker('EXIT')
|
||||||
|
|
||||||
|
private static final Monoid<Map<String, Object>> globalsMonoid = Monoids.of([:]) { m0, m1 ->
|
||||||
|
m0 + m1
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Monoid<Collection<TaskFactorySpec<TaskFactory>>> taskFactoriesMonoid =
|
||||||
|
Monoids.getMergeCollectionMonoid(TaskFactorySpec.SAME_NAME_AND_SUPPLIER_EQ, TaskFactorySpec.DEFAULT_SEMIGROUP)
|
||||||
|
|
||||||
|
private static <T> T reduceResults(
|
||||||
|
Collection<BuildDelegate.Results> resultsCollection,
|
||||||
|
Zero<T> tZero,
|
||||||
|
BiFunction<T, BuildDelegate.Results, T> resultsToT
|
||||||
|
) {
|
||||||
|
resultsCollection.inject(tZero.zero) { acc, r ->
|
||||||
|
resultsToT.apply(acc, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Collection<BuildDelegate.Results> mapBuildSpecsToResults(Collection<BuildSpec> buildSpecs) {
|
||||||
|
buildSpecs.collect {
|
||||||
|
def delegate = new BuildDelegate()
|
||||||
|
it.buildClosure.delegate = delegate
|
||||||
|
//noinspection UnnecessaryQualifiedReference
|
||||||
|
it.buildClosure.resolveStrategy = Closure.DELEGATE_FIRST
|
||||||
|
it.buildClosure()
|
||||||
|
new BuildDelegate.Results(delegate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Build toBuild(
|
||||||
|
Collection<BuildSpec> specs
|
||||||
|
) {
|
||||||
|
if (specs.empty) {
|
||||||
|
throw new IllegalArgumentException('specs must contain at least one BuildSpec')
|
||||||
|
}
|
||||||
|
def allResults = mapBuildSpecsToResults(specs)
|
||||||
|
def outputDirFunctionResult = reduceResults(allResults, OutputDirFunctions.DEFAULT_MONOID) { acc, r ->
|
||||||
|
r.getOutputDirFunctionResult(acc, { acc })
|
||||||
|
}
|
||||||
|
def siteSpecResult = reduceResults(allResults, SiteSpec.DEFAULT_MONOID) { acc, r ->
|
||||||
|
r.getSiteSpecResult(acc, true, SiteSpec.DEFAULT_MONOID)
|
||||||
|
}
|
||||||
|
def globalsResult = reduceResults(allResults, globalsMonoid) { acc, r ->
|
||||||
|
r.getGlobalsResult(acc, true, globalsMonoid)
|
||||||
|
}
|
||||||
|
|
||||||
|
def typesResult = reduceResults(allResults, TypesContainer.DEFAULT_MONOID) { acc, r ->
|
||||||
|
r.getTypesResult(acc, true, TypesContainer.DEFAULT_MONOID)
|
||||||
|
}
|
||||||
|
def sourcesResult = reduceResults(allResults, SourceProviders.DEFAULT_MONOID) { acc, r ->
|
||||||
|
r.getSourcesResult(acc, true, SourceProviders.DEFAULT_MONOID, typesResult)
|
||||||
|
}
|
||||||
|
def taskFactoriesResult = reduceResults(allResults, taskFactoriesMonoid) { acc, r ->
|
||||||
|
r.getTaskFactoriesResult(acc, true, taskFactoriesMonoid, sourcesResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
new Build(
|
||||||
|
specs.last().name,
|
||||||
|
outputDirFunctionResult,
|
||||||
|
siteSpecResult,
|
||||||
|
globalsResult,
|
||||||
|
taskFactoriesResult
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
static Collection<Build> getBuilds(Collection<BuildSpec> buildSpecs) {
|
||||||
|
logger.trace(enter, '')
|
||||||
|
def graph = BuildGraphUtil.getDependencyGraph(buildSpecs)
|
||||||
|
def r = new DepthFirstIterator<>(graph).findResults {
|
||||||
|
if (it.isAbstract) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
def ancestors = BuildGraphUtil.getAncestors(it, graph)
|
||||||
|
logger.debug('ancestors of {}: {}', it, ancestors)
|
||||||
|
toBuild([*ancestors, it])
|
||||||
|
}
|
||||||
|
logger.trace(exit, 'r: {}', r)
|
||||||
|
r
|
||||||
|
}
|
||||||
|
|
||||||
|
private BuildSpecUtil() {}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.html.PageToHtmlTaskFactory
|
||||||
|
import com.jessebrault.ssg.html.TextToHtmlSpecProviders
|
||||||
|
import com.jessebrault.ssg.html.TextToHtmlTaskFactory
|
||||||
|
import com.jessebrault.ssg.page.PageTypes
|
||||||
|
import com.jessebrault.ssg.page.PagesProviders
|
||||||
|
import com.jessebrault.ssg.part.PartTypes
|
||||||
|
import com.jessebrault.ssg.part.PartsProviders
|
||||||
|
import com.jessebrault.ssg.template.TemplateTypes
|
||||||
|
import com.jessebrault.ssg.template.TemplatesProviders
|
||||||
|
import com.jessebrault.ssg.text.TextTypes
|
||||||
|
import com.jessebrault.ssg.text.TextsProviders
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
@TupleConstructor(includeFields = true, defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode(includeFields = true)
|
||||||
|
final class DefaultBuildScriptConfiguratorFactory implements BuildScriptConfiguratorFactory {
|
||||||
|
|
||||||
|
private final File baseDir
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Consumer<BuildScriptBase> get() {
|
||||||
|
return {
|
||||||
|
it.build(name: 'default') {
|
||||||
|
outputDirFunction = { Build build ->
|
||||||
|
new OutputDir(new File(this.baseDir, build.name == 'default' ? 'build' : build.name))
|
||||||
|
}
|
||||||
|
|
||||||
|
types {
|
||||||
|
textTypes << TextTypes.MARKDOWN
|
||||||
|
pageTypes << PageTypes.GSP
|
||||||
|
templateTypes << TemplateTypes.GSP
|
||||||
|
partTypes << PartTypes.GSP
|
||||||
|
}
|
||||||
|
|
||||||
|
sources { base, types ->
|
||||||
|
texts TextsProviders.from(new File(this.baseDir, 'texts'), types.textTypes)
|
||||||
|
pages PagesProviders.from(new File(this.baseDir, 'pages'), types.pageTypes)
|
||||||
|
templates TemplatesProviders.from(new File(this.baseDir, 'templates'), types.templateTypes)
|
||||||
|
parts PartsProviders.from(new File(this.baseDir, 'parts'), types.partTypes)
|
||||||
|
}
|
||||||
|
|
||||||
|
taskFactories { base, sources ->
|
||||||
|
register('textToHtml', TextToHtmlTaskFactory::new) {
|
||||||
|
it.specProvider += TextToHtmlSpecProviders.from(sources)
|
||||||
|
it.allTextsProvider += sources.textsProvider
|
||||||
|
it.allPartsProvider += sources.partsProvider
|
||||||
|
it.allModelsProvider += sources.modelsProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
register('pageToHtml', PageToHtmlTaskFactory::new) {
|
||||||
|
it.pagesProvider += sources.pagesProvider
|
||||||
|
it.allTextsProvider += sources.textsProvider
|
||||||
|
it.allPartsProvider += sources.partsProvider
|
||||||
|
it.allModelsProvider += sources.modelsProvider
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript
|
||||||
|
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
|
||||||
|
@NullCheck
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class OutputDir {
|
||||||
|
|
||||||
|
private final String path
|
||||||
|
|
||||||
|
OutputDir(String path) {
|
||||||
|
this.path = path
|
||||||
|
}
|
||||||
|
|
||||||
|
OutputDir(File file) {
|
||||||
|
this(file.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
File asFile() {
|
||||||
|
new File(this.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
String asString() {
|
||||||
|
this.path
|
||||||
|
}
|
||||||
|
|
||||||
|
Object asType(Class<?> clazz) {
|
||||||
|
switch (clazz) {
|
||||||
|
case File -> this.asFile()
|
||||||
|
case String -> this.asString()
|
||||||
|
default -> throw new IllegalArgumentException('cannot cast to a class other than File or String')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"OutputDir(path: ${ this.path })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.util.Monoid
|
||||||
|
import com.jessebrault.ssg.util.Monoids
|
||||||
|
|
||||||
|
import java.util.function.Function
|
||||||
|
|
||||||
|
final class OutputDirFunctions {
|
||||||
|
|
||||||
|
static final Function<Build, OutputDir> DEFAULT = { Build build -> new OutputDir(build.name) }
|
||||||
|
|
||||||
|
static final Monoid<Function<Build, OutputDir>> DEFAULT_MONOID = Monoids.of(DEFAULT, OutputDirFunctions::concat)
|
||||||
|
|
||||||
|
static Function<Build, OutputDir> concat(
|
||||||
|
Function<Build, OutputDir> f0,
|
||||||
|
Function<Build, OutputDir> f1
|
||||||
|
) {
|
||||||
|
f0 == OutputDirFunctions.DEFAULT ? f1 : f0
|
||||||
|
}
|
||||||
|
|
||||||
|
static Function<Build, OutputDir> of(File dir) {
|
||||||
|
return { new OutputDir(dir) }
|
||||||
|
}
|
||||||
|
|
||||||
|
static Function<Build, OutputDir> of(String path) {
|
||||||
|
return { new OutputDir(path) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private OutputDirFunctions() {}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
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 com.jessebrault.ssg.util.Monoid
|
||||||
|
import com.jessebrault.ssg.util.Monoids
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@TupleConstructor(defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class SourceProviders {
|
||||||
|
|
||||||
|
static final Monoid<SourceProviders> DEFAULT_MONOID = Monoids.of(getEmpty(), SourceProviders::concat)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"SourceProviders(textsProvider: ${ this.textsProvider }, modelsProvider: ${ this.modelsProvider }, " +
|
||||||
|
"pagesProvider: ${ this.pagesProvider }, templatesProvider: ${ this.templatesProvider }, " +
|
||||||
|
"partsProvider: ${ this.partsProvider })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
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 com.jessebrault.ssg.util.Monoid
|
||||||
|
import com.jessebrault.ssg.util.Monoids
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@TupleConstructor(defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class TypesContainer {
|
||||||
|
|
||||||
|
static final Monoid<TypesContainer> DEFAULT_MONOID = Monoids.of(getEmpty(), TypesContainer::concat)
|
||||||
|
|
||||||
|
static TypesContainer getEmpty() {
|
||||||
|
new TypesContainer([], [], [], [])
|
||||||
|
}
|
||||||
|
|
||||||
|
static TypesContainer get(Map<String, Object> args) {
|
||||||
|
new TypesContainer(
|
||||||
|
args.textTypes ? args.textTypes as Collection<TextType> : [],
|
||||||
|
args.pageTypes ? args.pageTypes as Collection<PageType> : [],
|
||||||
|
args.templateTypes ? args.templateTypes as Collection<TemplateType> : [],
|
||||||
|
args.partTypes ? args.partTypes as Collection<PartType> : []
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
final Collection<TextType> textTypes
|
||||||
|
final Collection<PageType> pageTypes
|
||||||
|
final Collection<TemplateType> templateTypes
|
||||||
|
final Collection<PartType> partTypes
|
||||||
|
|
||||||
|
TypesContainer plus(TypesContainer other) {
|
||||||
|
concat(this, other)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"TypesContainer(textTypes: ${ this.textTypes }, pageTypes: ${ this.pageTypes }, " +
|
||||||
|
"templateTypes: ${ this.templateTypes }, partTypes: ${ this.partTypes })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,132 +1,287 @@
|
|||||||
package com.jessebrault.ssg.buildscript.delegates
|
package com.jessebrault.ssg.buildscript.delegates
|
||||||
|
|
||||||
import com.jessebrault.ssg.model.Model
|
import com.jessebrault.ssg.SiteSpec
|
||||||
import com.jessebrault.ssg.model.Models
|
import com.jessebrault.ssg.buildscript.Build
|
||||||
import com.jessebrault.ssg.text.MarkdownTextConverter
|
import com.jessebrault.ssg.buildscript.OutputDir
|
||||||
import com.jessebrault.ssg.text.TextConverter
|
import com.jessebrault.ssg.buildscript.SourceProviders
|
||||||
import com.jessebrault.ssg.util.PathUtil
|
import com.jessebrault.ssg.buildscript.TypesContainer
|
||||||
import com.jessebrault.di.DefaultRegistryObjectFactory
|
import com.jessebrault.ssg.mutable.Mutable
|
||||||
import com.jessebrault.di.RegistryObjectFactory
|
import com.jessebrault.ssg.mutable.Mutables
|
||||||
import com.jessebrault.fp.property.DefaultProperty
|
import com.jessebrault.ssg.task.TaskFactory
|
||||||
import com.jessebrault.fp.property.Property
|
import com.jessebrault.ssg.task.TaskFactorySpec
|
||||||
import com.jessebrault.fp.provider.DefaultProvider
|
import com.jessebrault.ssg.util.Monoid
|
||||||
import com.jessebrault.fp.provider.NamedProvider
|
import groovy.transform.EqualsAndHashCode
|
||||||
import com.jessebrault.fp.provider.Provider
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
import groovy.transform.stc.ClosureParams
|
||||||
|
import groovy.transform.stc.FromString
|
||||||
|
import groovy.transform.stc.SimpleType
|
||||||
|
|
||||||
import java.nio.file.Path
|
import java.util.function.Function
|
||||||
import java.util.function.Supplier
|
import java.util.function.Supplier
|
||||||
|
import java.util.function.UnaryOperator
|
||||||
|
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode(includeFields = true)
|
||||||
final class BuildDelegate {
|
final class BuildDelegate {
|
||||||
|
|
||||||
static BuildDelegate withDefaults(String buildName, File projectDir) {
|
@TupleConstructor(includeFields = true, defaults = false)
|
||||||
new BuildDelegate(projectDir).tap {
|
@NullCheck(includeGenerated = true)
|
||||||
basePackages.convention = [] as Set<String>
|
@EqualsAndHashCode(includeFields = true)
|
||||||
outputDir.convention = PathUtil.resolve(projectDir, Path.of('dist', buildName.split(/\\./)))
|
static final class Results {
|
||||||
globals.convention = [:]
|
|
||||||
models.convention = [] as Set<Model>
|
private final BuildDelegate delegate
|
||||||
textsDirs.convention = [new File(projectDir, 'texts')] as Set<File>
|
|
||||||
textConverters.convention = [new MarkdownTextConverter()] as Set<TextConverter>
|
Function<Build, OutputDir> getOutputDirFunctionResult(
|
||||||
objectFactoryBuilder.convention = DefaultRegistryObjectFactory.Builder.withDefaults()
|
Function<Build, OutputDir> base,
|
||||||
|
Supplier<Function<Build, OutputDir>> onEmpty
|
||||||
|
) {
|
||||||
|
this.delegate.outputDirFunction.getOrElse {
|
||||||
|
this.delegate.outputDirFunctionMapper.match(onEmpty) {
|
||||||
|
it.apply(base)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final File projectDir
|
SiteSpec getSiteSpecResult(
|
||||||
|
SiteSpec base,
|
||||||
final Property<Set<String>> basePackages = DefaultProperty.<Set<String>>empty(Set)
|
boolean onConcatWithBaseEmpty,
|
||||||
final Property<String> siteName = DefaultProperty.empty(String)
|
Monoid<SiteSpec> siteSpecMonoid
|
||||||
final Property<String> baseUrl = DefaultProperty.empty(String)
|
) {
|
||||||
final Property<File> outputDir = DefaultProperty.empty(File)
|
def concatWithBase = this.delegate.siteSpecConcatBase.isPresent()
|
||||||
final Property<Map<String, Object>> globals = DefaultProperty.<Map<String, Object>>empty(Map)
|
? this.delegate.siteSpecConcatBase.get()
|
||||||
final Property<Set<Model>> models = DefaultProperty.<Set<Model>>empty(Set)
|
: onConcatWithBaseEmpty
|
||||||
final Property<Set<File>> textsDirs = DefaultProperty.<Set<File>>empty(Set)
|
def onEmpty = { concatWithBase ? base : siteSpecMonoid.zero }
|
||||||
final Property<Set<TextConverter>> textConverters = DefaultProperty.<Set<TextConverter>>empty(Set)
|
this.delegate.siteSpecClosure.match(onEmpty) {
|
||||||
final Property<RegistryObjectFactory.Builder> objectFactoryBuilder =
|
def d = new SiteSpecDelegate(siteSpecMonoid)
|
||||||
DefaultProperty.empty(RegistryObjectFactory.Builder)
|
it.delegate = d
|
||||||
|
//noinspection UnnecessaryQualifiedReference
|
||||||
private BuildDelegate(File projectDir) {
|
it.resolveStrategy = Closure.DELEGATE_FIRST
|
||||||
this.projectDir = projectDir
|
it(base)
|
||||||
|
def r = d.getResult()
|
||||||
|
concatWithBase ? siteSpecMonoid.concat.apply(base, r) : r
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: add friendly DSL methods for setting all properties */
|
Map<String, Object> getGlobalsResult(
|
||||||
|
Map<String, Object> base,
|
||||||
void basePackage(String toAdd) {
|
boolean onConcatWithBaseEmpty,
|
||||||
this.basePackages.configure { it.add(toAdd) }
|
Monoid<Map<String, Object>> globalsMonoid
|
||||||
|
) {
|
||||||
|
def concatWithBase = this.delegate.globalsConcatBase.isPresent()
|
||||||
|
? this.delegate.globalsConcatBase.get()
|
||||||
|
: onConcatWithBaseEmpty
|
||||||
|
def onEmpty = { concatWithBase ? base : globalsMonoid.zero }
|
||||||
|
this.delegate.globalsClosure.match(onEmpty) {
|
||||||
|
def d = new GlobalsDelegate()
|
||||||
|
it.delegate = d
|
||||||
|
//noinspection UnnecessaryQualifiedReference
|
||||||
|
it.resolveStrategy = Closure.DELEGATE_FIRST
|
||||||
|
it(base)
|
||||||
|
def r = d.getResult()
|
||||||
|
concatWithBase ? globalsMonoid.concat.apply(base, r) : r
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void basePackages(String... toAdd) {
|
TypesContainer getTypesResult(
|
||||||
toAdd.each { this.basePackage(it) }
|
TypesContainer base,
|
||||||
|
boolean onConcatWithBaseEmpty,
|
||||||
|
Monoid<TypesContainer> typesContainerMonoid
|
||||||
|
) {
|
||||||
|
def concatWithBase = this.delegate.typesConcatBase.isPresent()
|
||||||
|
? this.delegate.typesConcatBase.get()
|
||||||
|
: onConcatWithBaseEmpty
|
||||||
|
def onEmpty = { concatWithBase ? base : typesContainerMonoid.zero }
|
||||||
|
this.delegate.typesClosure.match(onEmpty) {
|
||||||
|
def d = new TypesDelegate()
|
||||||
|
it.delegate = d
|
||||||
|
//noinspection UnnecessaryQualifiedReference
|
||||||
|
it.resolveStrategy = Closure.DELEGATE_FIRST
|
||||||
|
it(base)
|
||||||
|
def r = d.getResult()
|
||||||
|
concatWithBase ? typesContainerMonoid.concat.apply(base, r) : r
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void siteName(String siteName) {
|
SourceProviders getSourcesResult(
|
||||||
this.siteName.set(siteName)
|
SourceProviders base,
|
||||||
|
boolean onConcatWithBaseEmpty,
|
||||||
|
Monoid<SourceProviders> sourceProvidersMonoid,
|
||||||
|
TypesContainer types
|
||||||
|
) {
|
||||||
|
def concatWithBase = this.delegate.sourcesConcatBase.isPresent()
|
||||||
|
? this.delegate.sourcesConcatBase.get()
|
||||||
|
: onConcatWithBaseEmpty
|
||||||
|
def onEmpty = { concatWithBase ? base : sourceProvidersMonoid.zero }
|
||||||
|
this.delegate.sourcesClosure.match(onEmpty) {
|
||||||
|
def d = new SourceProvidersDelegate()
|
||||||
|
it.delegate = d
|
||||||
|
//noinspection UnnecessaryQualifiedReference
|
||||||
|
it.resolveStrategy = Closure.DELEGATE_FIRST
|
||||||
|
it(base, types)
|
||||||
|
def r = d.getResult()
|
||||||
|
concatWithBase ? sourceProvidersMonoid.concat.apply(base, r) : r
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void siteName(Provider<String> siteNameProvider) {
|
Collection<TaskFactorySpec<TaskFactory>> getTaskFactoriesResult(
|
||||||
this.siteName.set(siteNameProvider)
|
Collection<TaskFactorySpec<TaskFactory>> base,
|
||||||
|
boolean onConcatWithBaseEmpty,
|
||||||
|
Monoid<Collection<TaskFactorySpec<TaskFactory>>> taskFactorySpecsMonoid,
|
||||||
|
SourceProviders sources
|
||||||
|
) {
|
||||||
|
def concatWithBase = this.delegate.taskFactoriesConcatBase.isPresent()
|
||||||
|
? this.delegate.taskFactoriesConcatBase.get()
|
||||||
|
: onConcatWithBaseEmpty
|
||||||
|
def onEmpty = { concatWithBase ? base : taskFactorySpecsMonoid.zero }
|
||||||
|
this.delegate.taskFactoriesClosure.match(onEmpty) {
|
||||||
|
def d = new TaskFactoriesDelegate()
|
||||||
|
it.delegate = d
|
||||||
|
//noinspection UnnecessaryQualifiedReference
|
||||||
|
it.resolveStrategy = Closure.DELEGATE_FIRST
|
||||||
|
it(base, sources)
|
||||||
|
def r = d.getResult()
|
||||||
|
concatWithBase ? taskFactorySpecsMonoid.concat.apply(base, r) : r
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void baseUrl(String baseUrl) {
|
|
||||||
this.baseUrl.set(baseUrl)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void baseUrl(Provider<String> baseUrlProvider) {
|
private final Mutable<Function<Build, OutputDir>> outputDirFunction = Mutables.getEmpty()
|
||||||
this.baseUrl.set(baseUrlProvider)
|
private final Mutable<UnaryOperator<Function<Build, OutputDir>>> outputDirFunctionMapper = Mutables.getEmpty()
|
||||||
|
|
||||||
|
private final Mutable<Boolean> siteSpecConcatBase = Mutables.getEmpty()
|
||||||
|
private final Mutable<Closure<?>> siteSpecClosure = Mutables.getEmpty()
|
||||||
|
|
||||||
|
private final Mutable<Boolean> globalsConcatBase = Mutables.getEmpty()
|
||||||
|
private final Mutable<Closure<?>> globalsClosure = Mutables.getEmpty()
|
||||||
|
|
||||||
|
private final Mutable<Boolean> typesConcatBase = Mutables.getEmpty()
|
||||||
|
private final Mutable<Closure<?>> typesClosure = Mutables.getEmpty()
|
||||||
|
|
||||||
|
private final Mutable<Boolean> sourcesConcatBase = Mutables.getEmpty()
|
||||||
|
private final Mutable<Closure<?>> sourcesClosure = Mutables.getEmpty()
|
||||||
|
|
||||||
|
private final Mutable<Boolean> taskFactoriesConcatBase = Mutables.getEmpty()
|
||||||
|
private final Mutable<Closure<?>> taskFactoriesClosure = Mutables.getEmpty()
|
||||||
|
|
||||||
|
void setOutputDirFunction(Function<Build, OutputDir> outputDirFunction) {
|
||||||
|
this.outputDirFunction.set(outputDirFunction)
|
||||||
}
|
}
|
||||||
|
|
||||||
void outputDir(File outputDir) {
|
void setOutputDir(File file) {
|
||||||
this.outputDir.set(outputDir)
|
this.outputDirFunction.set { new OutputDir(file) }
|
||||||
}
|
}
|
||||||
|
|
||||||
void outputDir(Provider<File> outputDirProvider) {
|
void setOutputDir(String path) {
|
||||||
this.outputDir.set(outputDirProvider)
|
this.outputDirFunction.set { new OutputDir(path) }
|
||||||
}
|
}
|
||||||
|
|
||||||
void globals(@DelegatesTo(value = GlobalsDelegate, strategy = Closure.DELEGATE_FIRST) Closure globalsClosure) {
|
// Maps the *base*
|
||||||
def globalsDelegate = new GlobalsDelegate()
|
void outputDirFunction(UnaryOperator<Function<Build, OutputDir>> mapper) {
|
||||||
globalsClosure.delegate = globalsDelegate
|
this.outputDirFunctionMapper.set(mapper)
|
||||||
globalsClosure.resolveStrategy = Closure.DELEGATE_FIRST
|
|
||||||
globalsClosure()
|
|
||||||
this.globals.set this.globals.get() + globalsDelegate
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void model(String name, Object obj) {
|
void siteSpec(
|
||||||
this.models.configure {it.add(Models.of(name, obj)) }
|
@DelegatesTo(value = SiteSpecDelegate, strategy = Closure.DELEGATE_FIRST)
|
||||||
|
@ClosureParams(value = SimpleType, options = 'com.jessebrault.ssg.SiteSpec')
|
||||||
|
Closure<?> siteSpecClosure
|
||||||
|
) {
|
||||||
|
this.siteSpec(true, siteSpecClosure)
|
||||||
}
|
}
|
||||||
|
|
||||||
void model(Model model) {
|
void siteSpec(
|
||||||
this.models.configure { it.add(model) }
|
boolean concatWithBase,
|
||||||
|
@DelegatesTo(value = SiteSpecDelegate, strategy = Closure.DELEGATE_FIRST)
|
||||||
|
@ClosureParams(value = SimpleType, options = 'com.jessebrault.ssg.SiteSpec')
|
||||||
|
Closure<?> siteSpecClosure
|
||||||
|
) {
|
||||||
|
this.siteSpecConcatBase.set(concatWithBase)
|
||||||
|
this.siteSpecClosure.set(siteSpecClosure)
|
||||||
}
|
}
|
||||||
|
|
||||||
void model(String name, Provider tProvider) {
|
void globals(
|
||||||
this.models.configure { it.add(Models.ofProvider(name, tProvider)) }
|
@DelegatesTo(value = GlobalsDelegate, strategy = Closure.DELEGATE_FIRST)
|
||||||
|
@ClosureParams(value = FromString, options = 'Map<String, Object>')
|
||||||
|
Closure<?> globalsClosure
|
||||||
|
) {
|
||||||
|
this.globals(true, globalsClosure)
|
||||||
}
|
}
|
||||||
|
|
||||||
<T> void model(String name, Class<T> type, Supplier<? extends T> tSupplier) {
|
void globals(
|
||||||
this.models.configure { it.add(Models.ofSupplier(name, type, tSupplier)) }
|
boolean concatWithBase,
|
||||||
|
@DelegatesTo(value = GlobalsDelegate, strategy = Closure.DELEGATE_FIRST)
|
||||||
|
@ClosureParams(value = FromString, options = 'Map<String, Object>')
|
||||||
|
Closure<?> globalsClosure
|
||||||
|
) {
|
||||||
|
this.globalsConcatBase.set(concatWithBase)
|
||||||
|
this.globalsClosure.set(globalsClosure)
|
||||||
}
|
}
|
||||||
|
|
||||||
void model(NamedProvider namedProvider) {
|
void types(
|
||||||
this.models.configure { it.add(Models.ofNamedProvider(namedProvider)) }
|
@DelegatesTo(value = TypesDelegate, strategy = Closure.DELEGATE_FIRST)
|
||||||
|
@ClosureParams(value = SimpleType, options = 'com.jessebrault.ssg.buildscript.TypesContainer')
|
||||||
|
Closure<?> typesClosure
|
||||||
|
) {
|
||||||
|
this.types(true, typesClosure)
|
||||||
}
|
}
|
||||||
|
|
||||||
void textsDir(File textsDir) {
|
void types(
|
||||||
this.textsDirs.configure { it.add(textsDir) }
|
boolean concatWithBase,
|
||||||
|
@DelegatesTo(value = TypesDelegate, strategy = Closure.DELEGATE_FIRST)
|
||||||
|
@ClosureParams(value = SimpleType, options = 'com.jessebrault.ssg.buildscript.TypesContainer')
|
||||||
|
Closure<?> typesClosure
|
||||||
|
) {
|
||||||
|
this.typesConcatBase.set(concatWithBase)
|
||||||
|
this.typesClosure.set(typesClosure)
|
||||||
}
|
}
|
||||||
|
|
||||||
void textsDirs(File... textsDirs) {
|
void sources(
|
||||||
textsDirs.each { this.textsDir(it) }
|
@DelegatesTo(value = SourceProvidersDelegate, strategy = Closure.DELEGATE_FIRST)
|
||||||
|
@ClosureParams(
|
||||||
|
value = FromString,
|
||||||
|
options = 'com.jessebrault.ssg.buildscript.SourceProviders, com.jessebrault.ssg.buildscript.TypesContainer'
|
||||||
|
)
|
||||||
|
Closure<?> sourcesClosure
|
||||||
|
) {
|
||||||
|
this.sources(true, sourcesClosure)
|
||||||
}
|
}
|
||||||
|
|
||||||
void textConverter(TextConverter textConverter) {
|
void sources(
|
||||||
this.textConverters.configure { it.add(textConverter) }
|
boolean concatWithBase,
|
||||||
|
@DelegatesTo(value = SourceProvidersDelegate, strategy = Closure.DELEGATE_FIRST)
|
||||||
|
@ClosureParams(
|
||||||
|
value = FromString,
|
||||||
|
options = 'com.jessebrault.ssg.buildscript.SourceProviders, com.jessebrault.ssg.buildscript.TypesContainer'
|
||||||
|
)
|
||||||
|
Closure<?> sourcesClosure
|
||||||
|
) {
|
||||||
|
this.sourcesConcatBase.set(concatWithBase)
|
||||||
|
this.sourcesClosure.set(sourcesClosure)
|
||||||
}
|
}
|
||||||
|
|
||||||
void textConverters(TextConverter... textConverters) {
|
void taskFactories(
|
||||||
textConverters.each { this.textConverter(it) }
|
@DelegatesTo(value = TaskFactoriesDelegate, strategy = Closure.DELEGATE_FIRST)
|
||||||
|
@ClosureParams(
|
||||||
|
value = FromString,
|
||||||
|
options = 'java.util.Collection<com.jessebrault.ssg.task.TaskFactorySpec<com.jessebrault.ssg.task.TaskFactory>>, com.jessebrault.ssg.buildscript.SourceProviders'
|
||||||
|
)
|
||||||
|
Closure<?> taskFactoriesClosure
|
||||||
|
) {
|
||||||
|
this.taskFactories(true, taskFactoriesClosure)
|
||||||
}
|
}
|
||||||
|
|
||||||
void objectFactoryBuilder(RegistryObjectFactory.Builder builder) {
|
void taskFactories(
|
||||||
this.objectFactoryBuilder.set(builder)
|
boolean concatWithBase,
|
||||||
|
@DelegatesTo(value = TaskFactoriesDelegate, strategy = Closure.DELEGATE_FIRST)
|
||||||
|
@ClosureParams(
|
||||||
|
value = FromString,
|
||||||
|
options = 'java.util.Collection<com.jessebrault.ssg.task.TaskFactorySpec<com.jessebrault.ssg.task.TaskFactory>>, com.jessebrault.ssg.buildscript.SourceProviders'
|
||||||
|
)
|
||||||
|
Closure<?> taskFactoriesClosure
|
||||||
|
) {
|
||||||
|
this.taskFactoriesConcatBase.set(concatWithBase)
|
||||||
|
this.taskFactoriesClosure.set(taskFactoriesClosure)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript.delegates
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.SiteSpec
|
||||||
|
import com.jessebrault.ssg.util.Zero
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull
|
||||||
|
|
||||||
|
final class SiteSpecDelegate {
|
||||||
|
|
||||||
|
private String name
|
||||||
|
private String baseUrl
|
||||||
|
|
||||||
|
SiteSpecDelegate(Zero<SiteSpec> siteSpecZero) {
|
||||||
|
this.name = siteSpecZero.zero.name
|
||||||
|
this.baseUrl = siteSpecZero.zero.baseUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
String getName() {
|
||||||
|
return this.name
|
||||||
|
}
|
||||||
|
|
||||||
|
void setName(String name) {
|
||||||
|
this.name = requireNonNull(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
String getBaseUrl() {
|
||||||
|
return this.baseUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
void setBaseUrl(String baseUrl) {
|
||||||
|
this.baseUrl = requireNonNull(baseUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
SiteSpec getResult() {
|
||||||
|
new SiteSpec(this.name, this.baseUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript.delegates
|
||||||
|
|
||||||
|
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
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
|
||||||
|
@NullCheck
|
||||||
|
@EqualsAndHashCode
|
||||||
|
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,66 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript.delegates
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.task.TaskFactory
|
||||||
|
import com.jessebrault.ssg.task.TaskFactorySpec
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
|
||||||
|
import java.util.function.Consumer
|
||||||
|
import java.util.function.Supplier
|
||||||
|
|
||||||
|
@NullCheck
|
||||||
|
@EqualsAndHashCode(includeFields = true)
|
||||||
|
final class TaskFactoriesDelegate {
|
||||||
|
|
||||||
|
private final Collection<TaskFactorySpec<TaskFactory>> specs = []
|
||||||
|
|
||||||
|
private boolean isRegistered(String name) {
|
||||||
|
this.specs.find { it.name == name } != null
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkNotRegistered(String name) {
|
||||||
|
if (this.isRegistered(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 << new TaskFactorySpec<>(name, factorySupplier, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
def <T extends TaskFactory> void register(
|
||||||
|
String name,
|
||||||
|
Supplier<T> factorySupplier,
|
||||||
|
Consumer<T> factoryConfigurator
|
||||||
|
) {
|
||||||
|
this.checkNotRegistered(name)
|
||||||
|
this.specs << new TaskFactorySpec<>(name, factorySupplier, [factoryConfigurator])
|
||||||
|
}
|
||||||
|
|
||||||
|
void register(TaskFactorySpec<TaskFactory> spec) {
|
||||||
|
this.specs << spec
|
||||||
|
}
|
||||||
|
|
||||||
|
void registerAll(Collection<TaskFactorySpec<TaskFactory>> specs) {
|
||||||
|
this.specs.addAll(specs)
|
||||||
|
}
|
||||||
|
|
||||||
|
def <T extends TaskFactory> void configure(
|
||||||
|
String name,
|
||||||
|
Class<T> factoryClass, // Dummy so we get better auto-complete
|
||||||
|
Consumer<T> factoryConfigurator
|
||||||
|
) {
|
||||||
|
if (!this.isRegistered(name)) {
|
||||||
|
throw new IllegalArgumentException("there is no TaskFactory registered by name ${ name }")
|
||||||
|
}
|
||||||
|
def spec = this.specs.find { it.name == name }
|
||||||
|
// Potentially dangerous, but the configurators Collection *should* only contain the correct types.
|
||||||
|
spec.configurators << (factoryConfigurator as Consumer<TaskFactory>)
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection<TaskFactorySpec<TaskFactory>> getResult() {
|
||||||
|
this.specs
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package com.jessebrault.ssg.buildscript.delegates
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,15 +0,0 @@
|
|||||||
package com.jessebrault.ssg.di
|
|
||||||
|
|
||||||
import jakarta.inject.Qualifier
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType
|
|
||||||
import java.lang.annotation.Retention
|
|
||||||
import java.lang.annotation.RetentionPolicy
|
|
||||||
import java.lang.annotation.Target
|
|
||||||
|
|
||||||
@Qualifier
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Target([ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD])
|
|
||||||
@interface Global {
|
|
||||||
String value()
|
|
||||||
}
|
|
@ -1,43 +0,0 @@
|
|||||||
package com.jessebrault.ssg.di
|
|
||||||
|
|
||||||
import groovy.transform.TupleConstructor
|
|
||||||
import com.jessebrault.di.Binding
|
|
||||||
import com.jessebrault.di.QualifierHandler
|
|
||||||
import com.jessebrault.di.QualifierHandlerContainer
|
|
||||||
import com.jessebrault.di.RegistryExtension
|
|
||||||
import com.jessebrault.di.SingletonBinding
|
|
||||||
|
|
||||||
import java.lang.annotation.Annotation
|
|
||||||
|
|
||||||
class GlobalsExtension implements QualifierHandlerContainer, RegistryExtension {
|
|
||||||
|
|
||||||
@TupleConstructor(includeFields = true)
|
|
||||||
static class GlobalQualifierHandler implements QualifierHandler<Global> {
|
|
||||||
|
|
||||||
private final GlobalsExtension extension
|
|
||||||
|
|
||||||
@Override
|
|
||||||
<T> Binding<T> handle(Global global, Class<T> aClass) {
|
|
||||||
if (extension.globals.containsKey(global.value())) {
|
|
||||||
return new SingletonBinding<T>(extension.globals.get(global.value()) as T)
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("There is no global for ${global.value()}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
final Map<String, Object> globals = [:]
|
|
||||||
|
|
||||||
private final GlobalQualifierHandler globalQualifierHandler = new GlobalQualifierHandler(this)
|
|
||||||
|
|
||||||
@Override
|
|
||||||
<A extends Annotation> QualifierHandler<A> getQualifierHandler(Class<A> aClass) {
|
|
||||||
if (Global.is(aClass)) {
|
|
||||||
return this.globalQualifierHandler as QualifierHandler<A>
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package com.jessebrault.ssg.di
|
|
||||||
|
|
||||||
import jakarta.inject.Qualifier
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType
|
|
||||||
import java.lang.annotation.Retention
|
|
||||||
import java.lang.annotation.RetentionPolicy
|
|
||||||
import java.lang.annotation.Target
|
|
||||||
|
|
||||||
@Qualifier
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Target([ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD])
|
|
||||||
@interface InjectModel {
|
|
||||||
String value()
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
package com.jessebrault.ssg.di
|
|
||||||
|
|
||||||
import groovy.transform.TupleConstructor
|
|
||||||
import com.jessebrault.di.Binding
|
|
||||||
import com.jessebrault.di.QualifierHandler
|
|
||||||
import com.jessebrault.di.SingletonBinding
|
|
||||||
|
|
||||||
@TupleConstructor(includeFields = true)
|
|
||||||
class InjectModelQualifierHandler implements QualifierHandler<InjectModel> {
|
|
||||||
|
|
||||||
private final ModelsExtension extension
|
|
||||||
|
|
||||||
@Override
|
|
||||||
<T> Binding<T> handle(InjectModel injectModel, Class<T> requestedClass) {
|
|
||||||
def found = this.extension.allModels.find {
|
|
||||||
requestedClass.isAssignableFrom(it.class) && it.name == injectModel.value()
|
|
||||||
}
|
|
||||||
if (found == null) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Could not find a Model with name ${injectModel.value()} and/or type $requestedClass.name"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
new SingletonBinding<T>(found.get() as T)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
package com.jessebrault.ssg.di
|
|
||||||
|
|
||||||
import jakarta.inject.Qualifier
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType
|
|
||||||
import java.lang.annotation.Retention
|
|
||||||
import java.lang.annotation.RetentionPolicy
|
|
||||||
import java.lang.annotation.Target
|
|
||||||
|
|
||||||
@Qualifier
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Target([ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD])
|
|
||||||
@interface InjectModels {
|
|
||||||
String[] value()
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
package com.jessebrault.ssg.di
|
|
||||||
|
|
||||||
import groovy.transform.TupleConstructor
|
|
||||||
import com.jessebrault.di.Binding
|
|
||||||
import com.jessebrault.di.QualifierHandler
|
|
||||||
import com.jessebrault.di.SingletonBinding
|
|
||||||
|
|
||||||
@TupleConstructor(includeFields = true)
|
|
||||||
class InjectModelsQualifierHandler implements QualifierHandler<InjectModels> {
|
|
||||||
|
|
||||||
private final ModelsExtension extension
|
|
||||||
|
|
||||||
@Override
|
|
||||||
<T> Binding<T> handle(InjectModels injectModels, Class<T> requestedType) {
|
|
||||||
if (!List.is(requestedType)) {
|
|
||||||
throw new IllegalArgumentException("@InjectModels must be used with List.")
|
|
||||||
}
|
|
||||||
def allFound = this.extension.allModels.inject([] as T) { acc, model ->
|
|
||||||
if (model.type.isAssignableFrom(requestedType) && model.name in injectModels.value()) {
|
|
||||||
acc << model.get()
|
|
||||||
}
|
|
||||||
acc
|
|
||||||
}
|
|
||||||
new SingletonBinding<T>(allFound as T)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package com.jessebrault.ssg.di
|
|
||||||
|
|
||||||
import jakarta.inject.Qualifier
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType
|
|
||||||
import java.lang.annotation.Retention
|
|
||||||
import java.lang.annotation.RetentionPolicy
|
|
||||||
import java.lang.annotation.Target
|
|
||||||
|
|
||||||
@Qualifier
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Target([ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD])
|
|
||||||
@interface InjectPage {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* May be either a page name or a path starting with '/'
|
|
||||||
*/
|
|
||||||
String value()
|
|
||||||
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
package com.jessebrault.ssg.di
|
|
||||||
|
|
||||||
import com.jessebrault.ssg.page.Page
|
|
||||||
import groovy.transform.TupleConstructor
|
|
||||||
import com.jessebrault.di.Binding
|
|
||||||
import com.jessebrault.di.QualifierHandler
|
|
||||||
import com.jessebrault.di.SingletonBinding
|
|
||||||
|
|
||||||
@TupleConstructor(includeFields = true)
|
|
||||||
class InjectPageQualifierHandler implements QualifierHandler<InjectPage> {
|
|
||||||
|
|
||||||
private final PagesExtension pagesExtension
|
|
||||||
|
|
||||||
@Override
|
|
||||||
<T> Binding<T> handle(InjectPage injectPage, Class<T> requestedClass) {
|
|
||||||
if (!Page.isAssignableFrom(requestedClass)) {
|
|
||||||
throw new IllegalArgumentException("Cannot inject a Page into a non-Page parameter/setter/field.")
|
|
||||||
}
|
|
||||||
def requested = injectPage.value()
|
|
||||||
def found = this.pagesExtension.allPages.find {
|
|
||||||
if (requested.startsWith('/')) {
|
|
||||||
it.path == requested
|
|
||||||
} else {
|
|
||||||
it.name == requested
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (found == null) {
|
|
||||||
throw new IllegalArgumentException("Cannot find a page with the following name or path: $requested")
|
|
||||||
}
|
|
||||||
new SingletonBinding<T>(found as T)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package com.jessebrault.ssg.di
|
|
||||||
|
|
||||||
import jakarta.inject.Qualifier
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType
|
|
||||||
import java.lang.annotation.Retention
|
|
||||||
import java.lang.annotation.RetentionPolicy
|
|
||||||
import java.lang.annotation.Target
|
|
||||||
|
|
||||||
@Qualifier
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Target([ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD])
|
|
||||||
@interface InjectPages {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Names of pages and/or globs (starting with '/') of pages
|
|
||||||
*/
|
|
||||||
String[] value()
|
|
||||||
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
package com.jessebrault.ssg.di
|
|
||||||
|
|
||||||
import com.jessebrault.ssg.page.Page
|
|
||||||
import com.jessebrault.ssg.util.Glob
|
|
||||||
import groovy.transform.TupleConstructor
|
|
||||||
import com.jessebrault.di.Binding
|
|
||||||
import com.jessebrault.di.QualifierHandler
|
|
||||||
import com.jessebrault.di.SingletonBinding
|
|
||||||
|
|
||||||
@TupleConstructor(includeFields = true)
|
|
||||||
class InjectPagesQualifierHandler implements QualifierHandler<InjectPages> {
|
|
||||||
|
|
||||||
private final PagesExtension pagesExtension
|
|
||||||
|
|
||||||
@Override
|
|
||||||
<T> Binding<T> handle(InjectPages injectPages, Class<T> requestedClass) {
|
|
||||||
if (!(Set.isAssignableFrom(requestedClass))) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
'Cannot inject a Collection of Pages into a non-Collection parameter/setter/field.'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
def foundPages = [] as Set<Page>
|
|
||||||
for (final String requested : injectPages.value()) {
|
|
||||||
if (requested.startsWith('/')) {
|
|
||||||
def glob = new Glob(requested)
|
|
||||||
def allFound = this.pagesExtension.allPages.inject([] as Set<Page>) { acc, page ->
|
|
||||||
if (glob.matches(page.path)) {
|
|
||||||
acc << page
|
|
||||||
}
|
|
||||||
acc
|
|
||||||
}
|
|
||||||
allFound.each { foundPages << it }
|
|
||||||
} else {
|
|
||||||
def found = this.pagesExtension.allPages.find { it.name == requested }
|
|
||||||
if (found == null) {
|
|
||||||
throw new IllegalArgumentException("Cannot find page with the name: $requested")
|
|
||||||
}
|
|
||||||
foundPages << found
|
|
||||||
}
|
|
||||||
}
|
|
||||||
new SingletonBinding<T>(foundPages as T)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package com.jessebrault.ssg.di
|
|
||||||
|
|
||||||
import jakarta.inject.Qualifier
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType
|
|
||||||
import java.lang.annotation.Retention
|
|
||||||
import java.lang.annotation.RetentionPolicy
|
|
||||||
import java.lang.annotation.Target
|
|
||||||
|
|
||||||
@Qualifier
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Target([ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD])
|
|
||||||
@interface InjectText {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The name of the text, or the path of the text, starting with '/'
|
|
||||||
*/
|
|
||||||
String value()
|
|
||||||
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
package com.jessebrault.ssg.di
|
|
||||||
|
|
||||||
import com.jessebrault.ssg.text.Text
|
|
||||||
import groovy.transform.TupleConstructor
|
|
||||||
import com.jessebrault.di.Binding
|
|
||||||
import com.jessebrault.di.QualifierHandler
|
|
||||||
import com.jessebrault.di.SingletonBinding
|
|
||||||
|
|
||||||
@TupleConstructor(includeFields = true)
|
|
||||||
class InjectTextQualifierHandler implements QualifierHandler<InjectText> {
|
|
||||||
|
|
||||||
private final TextsExtension extension
|
|
||||||
|
|
||||||
@Override
|
|
||||||
<T> Binding<T> handle(InjectText injectText, Class<T> requestedClass) {
|
|
||||||
if (!Text.isAssignableFrom(requestedClass)) {
|
|
||||||
throw new IllegalArgumentException("Cannot @InjectText on a non-Text parameter/method/field.")
|
|
||||||
}
|
|
||||||
def found = this.extension.allTexts.find {
|
|
||||||
it.name == injectText.value() || it.path == injectText.value()
|
|
||||||
}
|
|
||||||
if (found == null) {
|
|
||||||
throw new IllegalArgumentException("Could not find a Text with name or path ${injectText.value()}")
|
|
||||||
}
|
|
||||||
new SingletonBinding<T>(found as T)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package com.jessebrault.ssg.di
|
|
||||||
|
|
||||||
import jakarta.inject.Qualifier
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType
|
|
||||||
import java.lang.annotation.Retention
|
|
||||||
import java.lang.annotation.RetentionPolicy
|
|
||||||
import java.lang.annotation.Target
|
|
||||||
|
|
||||||
@Qualifier
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Target([ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD])
|
|
||||||
@interface InjectTexts {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Names of texts and/or globs (starting with '/') of texts
|
|
||||||
*/
|
|
||||||
String[] value()
|
|
||||||
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
package com.jessebrault.ssg.di
|
|
||||||
|
|
||||||
import com.jessebrault.ssg.text.Text
|
|
||||||
import com.jessebrault.ssg.util.Glob
|
|
||||||
import groovy.transform.TupleConstructor
|
|
||||||
import com.jessebrault.di.Binding
|
|
||||||
import com.jessebrault.di.QualifierHandler
|
|
||||||
import com.jessebrault.di.SingletonBinding
|
|
||||||
|
|
||||||
@TupleConstructor(includeFields = true)
|
|
||||||
class InjectTextsQualifierHandler implements QualifierHandler<InjectTexts> {
|
|
||||||
|
|
||||||
private final TextsExtension extension
|
|
||||||
|
|
||||||
@Override
|
|
||||||
<T> Binding<T> handle(InjectTexts injectTexts, Class<T> aClass) {
|
|
||||||
if (!Set.is(aClass)) {
|
|
||||||
throw new IllegalArgumentException('Cannot @InjectTexts on a non-Set parameter/method/field.')
|
|
||||||
}
|
|
||||||
def allFound = injectTexts.value().inject([] as Set<Text>) { acc, nameOrPathGlob ->
|
|
||||||
if (nameOrPathGlob.startsWith('/')) {
|
|
||||||
def glob = new Glob(nameOrPathGlob)
|
|
||||||
def matching = this.extension.allTexts.inject([] as Set<Text>) { matchingAcc, text ->
|
|
||||||
if (glob.matches(text.path)) {
|
|
||||||
matchingAcc << text
|
|
||||||
}
|
|
||||||
matchingAcc
|
|
||||||
}
|
|
||||||
acc.addAll(matching)
|
|
||||||
} else {
|
|
||||||
def found = this.extension.allTexts.find { it.name == nameOrPathGlob }
|
|
||||||
acc << found
|
|
||||||
}
|
|
||||||
acc
|
|
||||||
}
|
|
||||||
new SingletonBinding<T>(allFound as T)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
package com.jessebrault.ssg.di
|
|
||||||
|
|
||||||
import com.jessebrault.ssg.model.Model
|
|
||||||
import com.jessebrault.di.QualifierHandler
|
|
||||||
import com.jessebrault.di.QualifierHandlerContainer
|
|
||||||
import com.jessebrault.di.RegistryExtension
|
|
||||||
|
|
||||||
import java.lang.annotation.Annotation
|
|
||||||
|
|
||||||
class ModelsExtension implements QualifierHandlerContainer, RegistryExtension {
|
|
||||||
|
|
||||||
final Set<Model> allModels = []
|
|
||||||
|
|
||||||
private final QualifierHandler<InjectModel> injectModelQualifierHandler = new InjectModelQualifierHandler(this)
|
|
||||||
private final QualifierHandler<InjectModels> injectModelsQualifierHandler = new InjectModelsQualifierHandler(this)
|
|
||||||
|
|
||||||
@Override
|
|
||||||
<A extends Annotation> QualifierHandler<A> getQualifierHandler(Class<A> aClass) {
|
|
||||||
if (aClass == InjectModel) {
|
|
||||||
return this.injectModelQualifierHandler as QualifierHandler<A>
|
|
||||||
} else if (aClass == InjectModels) {
|
|
||||||
return this.injectModelsQualifierHandler as QualifierHandler<A>
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
package com.jessebrault.ssg.di
|
|
||||||
|
|
||||||
import com.jessebrault.ssg.page.Page
|
|
||||||
import com.jessebrault.di.QualifierHandler
|
|
||||||
import com.jessebrault.di.QualifierHandlerContainer
|
|
||||||
import com.jessebrault.di.RegistryExtension
|
|
||||||
|
|
||||||
import java.lang.annotation.Annotation
|
|
||||||
|
|
||||||
class PagesExtension implements QualifierHandlerContainer, RegistryExtension {
|
|
||||||
|
|
||||||
final Set<Page> allPages = []
|
|
||||||
|
|
||||||
private final QualifierHandler<InjectPage> injectPage = new InjectPageQualifierHandler(this)
|
|
||||||
private final QualifierHandler<InjectPages> injectPages = new InjectPagesQualifierHandler(this)
|
|
||||||
|
|
||||||
@Override
|
|
||||||
<A extends Annotation> QualifierHandler<A> getQualifierHandler(Class<A> annotationClass) {
|
|
||||||
if (annotationClass == InjectPage) {
|
|
||||||
return this.injectPage as QualifierHandler<A>
|
|
||||||
} else if (annotationClass == InjectPages) {
|
|
||||||
return this.injectPages as QualifierHandler<A>
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package com.jessebrault.ssg.di
|
|
||||||
|
|
||||||
import jakarta.inject.Qualifier
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType
|
|
||||||
import java.lang.annotation.Retention
|
|
||||||
import java.lang.annotation.RetentionPolicy
|
|
||||||
import java.lang.annotation.Target
|
|
||||||
|
|
||||||
@Qualifier
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Target([ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD])
|
|
||||||
@interface SelfPage {}
|
|
@ -1,42 +0,0 @@
|
|||||||
package com.jessebrault.ssg.di
|
|
||||||
|
|
||||||
import com.jessebrault.ssg.page.Page
|
|
||||||
import groovy.transform.TupleConstructor
|
|
||||||
import com.jessebrault.di.*
|
|
||||||
|
|
||||||
import java.lang.annotation.Annotation
|
|
||||||
|
|
||||||
class SelfPageExtension implements RegistryExtension, QualifierHandlerContainer {
|
|
||||||
|
|
||||||
@TupleConstructor(includeFields = true)
|
|
||||||
static class SelfPageQualifierHandler implements QualifierHandler<SelfPage> {
|
|
||||||
|
|
||||||
private final SelfPageExtension extension
|
|
||||||
|
|
||||||
@Override
|
|
||||||
<T> Binding<T> handle(SelfPage selfPage, Class<T> requestedType) {
|
|
||||||
if (!Page.class.isAssignableFrom(requestedType)) {
|
|
||||||
throw new IllegalArgumentException('Cannot put @SelfPage on a non-Page parameter/method/field.')
|
|
||||||
}
|
|
||||||
if (this.extension.currentPage == null) {
|
|
||||||
throw new IllegalStateException('Cannot get @SelfPage because extension.currentPage is null.')
|
|
||||||
}
|
|
||||||
new SingletonBinding<T>(this.extension.currentPage as T)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Page currentPage
|
|
||||||
|
|
||||||
private final SelfPageQualifierHandler selfPageQualifierHandler = new SelfPageQualifierHandler(this)
|
|
||||||
|
|
||||||
@Override
|
|
||||||
<A extends Annotation> QualifierHandler<A> getQualifierHandler(Class<A> annotationType) {
|
|
||||||
if (SelfPage.is(annotationType)) {
|
|
||||||
return this.selfPageQualifierHandler as QualifierHandler<A>
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,229 +0,0 @@
|
|||||||
package com.jessebrault.ssg.di;
|
|
||||||
|
|
||||||
import com.jessebrault.di.*;
|
|
||||||
import jakarta.inject.Named;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
public class SsgNamedRegistryExtension implements NamedRegistryExtension {
|
|
||||||
|
|
||||||
protected static void checkName(String name) {
|
|
||||||
if (name.startsWith(":") || name.endsWith(":")) {
|
|
||||||
throw new IllegalArgumentException(
|
|
||||||
"Illegal ssg @Named format: cannot start or end with colon (':'); given: " + name
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static @Nullable String getPrefix(String fullName) {
|
|
||||||
final var firstColon = fullName.indexOf(":");
|
|
||||||
if (firstColon == -1) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return fullName.substring(0, firstColon);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static String getAfterPrefix(String fullName) {
|
|
||||||
final int firstColon = fullName.indexOf(":");
|
|
||||||
if (firstColon == -1) {
|
|
||||||
return fullName;
|
|
||||||
} else {
|
|
||||||
return fullName.substring(firstColon + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static boolean hasPrefix(String fullName) {
|
|
||||||
return fullName.contains(":");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static String getSimplePrefix(Class<?> dependencyClass) {
|
|
||||||
final String simpleTypeName = dependencyClass.getSimpleName();
|
|
||||||
final String simpleTypeNameStart = simpleTypeName.substring(0, 1).toLowerCase();
|
|
||||||
final String uncapitalizedSimpleTypeName;
|
|
||||||
if (simpleTypeName.length() > 1) {
|
|
||||||
uncapitalizedSimpleTypeName = simpleTypeNameStart + simpleTypeName.substring(1);
|
|
||||||
} else {
|
|
||||||
uncapitalizedSimpleTypeName = simpleTypeNameStart;
|
|
||||||
}
|
|
||||||
return uncapitalizedSimpleTypeName;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static String getCanonicalPrefix(Class<?> dependencyClass) {
|
|
||||||
return dependencyClass.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class SsgNamedQualifierHandler implements QualifierHandler<Named> {
|
|
||||||
|
|
||||||
private final SsgNamedRegistryExtension extension;
|
|
||||||
|
|
||||||
public SsgNamedQualifierHandler(SsgNamedRegistryExtension extension) {
|
|
||||||
this.extension = extension;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @Nullable <T> Binding<T> handle(Named annotation, Class<T> dependencyClass) {
|
|
||||||
return this.extension.getBinding(new SimpleKeyHolder<>(
|
|
||||||
SsgNamedRegistryExtension.class,
|
|
||||||
dependencyClass,
|
|
||||||
annotation.value()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class SsgNamedWithPrefixKeyHolder<T> implements KeyHolder<NamedRegistryExtension, String, T> {
|
|
||||||
|
|
||||||
private final Class<T> dependencyType;
|
|
||||||
private final @Nullable String prefix;
|
|
||||||
private final String afterPrefix;
|
|
||||||
|
|
||||||
public SsgNamedWithPrefixKeyHolder(
|
|
||||||
Class<T> dependencyType,
|
|
||||||
@Nullable String prefix,
|
|
||||||
String afterPrefix
|
|
||||||
) {
|
|
||||||
this.dependencyType = dependencyType;
|
|
||||||
this.afterPrefix = afterPrefix;
|
|
||||||
this.prefix = prefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<NamedRegistryExtension> binderType() {
|
|
||||||
return NamedRegistryExtension.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<T> type() {
|
|
||||||
return this.dependencyType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String key() {
|
|
||||||
return this.afterPrefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable String prefix() {
|
|
||||||
return this.prefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) return true;
|
|
||||||
if (obj instanceof SsgNamedWithPrefixKeyHolder<?> other) {
|
|
||||||
return this.dependencyType.equals(other.type())
|
|
||||||
&& this.key().equals(other.key())
|
|
||||||
&& Objects.equals(this.prefix(), other.prefix());
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = dependencyType.hashCode();
|
|
||||||
result = 31 * result + afterPrefix.hashCode();
|
|
||||||
result = 31 * result + Objects.hashCode(prefix);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Map<KeyHolder<NamedRegistryExtension, String, ?>, Binding<?>> bindings = new HashMap<>();
|
|
||||||
private final QualifierHandler<Named> qualifierHandler = this.getNamedQualifierHandler();
|
|
||||||
|
|
||||||
protected QualifierHandler<Named> getNamedQualifierHandler() {
|
|
||||||
return new SsgNamedQualifierHandler(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class<String> getKeyClass() {
|
|
||||||
return String.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <B extends KeyBinder<String>, T> void bind(
|
|
||||||
KeyHolder<B, ? extends String, T> keyHolder,
|
|
||||||
Consumer<? super BindingConfigurator<T>> configure
|
|
||||||
) {
|
|
||||||
final var configurator = new SimpleBindingConfigurator<>(keyHolder.type());
|
|
||||||
configure.accept(configurator);
|
|
||||||
final String fullName = keyHolder.key();
|
|
||||||
checkName(fullName);
|
|
||||||
if (hasPrefix(fullName)) {
|
|
||||||
this.bindings.put(new SsgNamedWithPrefixKeyHolder<>(
|
|
||||||
keyHolder.type(),
|
|
||||||
getPrefix(fullName),
|
|
||||||
getAfterPrefix(fullName)
|
|
||||||
), configurator.getBinding());
|
|
||||||
} else {
|
|
||||||
this.bindings.put(new SimpleKeyHolder<>(
|
|
||||||
NamedRegistryExtension.class,
|
|
||||||
keyHolder.type(),
|
|
||||||
keyHolder.key()
|
|
||||||
), configurator.getBinding());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public @Nullable <B extends KeyBinder<String>, T> Binding<T> getBinding(
|
|
||||||
KeyHolder<B, ? extends String, T> keyHolder
|
|
||||||
) {
|
|
||||||
final String fullName = keyHolder.key();
|
|
||||||
checkName(fullName);
|
|
||||||
if (hasPrefix(fullName)) {
|
|
||||||
if (keyHolder instanceof SsgNamedWithPrefixKeyHolder<?> && this.bindings.containsKey(keyHolder)) {
|
|
||||||
return (Binding<T>) this.bindings.get(keyHolder);
|
|
||||||
} else {
|
|
||||||
final String afterPrefix = getAfterPrefix(fullName);
|
|
||||||
final Class<T> type = keyHolder.type();
|
|
||||||
|
|
||||||
final @Nullable Binding<T> withSimple = (Binding<T>) this.bindings.get(
|
|
||||||
new SsgNamedWithPrefixKeyHolder<>(type, afterPrefix, getSimplePrefix(type))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (withSimple != null) {
|
|
||||||
return withSimple;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (Binding<T>) this.bindings.get(new SsgNamedWithPrefixKeyHolder<>(
|
|
||||||
type, afterPrefix, getCanonicalPrefix(type)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return (Binding<T>) this.bindings.getOrDefault(keyHolder, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <B extends KeyBinder<String>, T> void removeBinding(KeyHolder<B, ? extends String, T> keyHolder) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <B extends KeyBinder<String>, T> void removeBindingIf(
|
|
||||||
KeyHolder<B, ? extends String, T> keyHolder,
|
|
||||||
Predicate<? super Binding<T>> filter
|
|
||||||
) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clearAllBindings() {
|
|
||||||
this.bindings.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public @Nullable <A extends Annotation> QualifierHandler<A> getQualifierHandler(Class<A> qualifierType) {
|
|
||||||
return (QualifierHandler<A>) this.qualifierHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package com.jessebrault.ssg.di
|
|
||||||
|
|
||||||
import com.jessebrault.di.DefaultRegistryObjectFactory
|
|
||||||
import com.jessebrault.di.RegistryObjectFactory
|
|
||||||
|
|
||||||
final class SsgObjectFactoryUtil {
|
|
||||||
|
|
||||||
static RegistryObjectFactory getDefault() {
|
|
||||||
DefaultRegistryObjectFactory.Builder.withDefaults().with {
|
|
||||||
it.configureRegistry { registry ->
|
|
||||||
registry.addExtension(new PagesExtension())
|
|
||||||
}
|
|
||||||
build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private SsgObjectFactoryUtil() {}
|
|
||||||
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
package com.jessebrault.ssg.di
|
|
||||||
|
|
||||||
import com.jessebrault.ssg.text.Text
|
|
||||||
import com.jessebrault.di.QualifierHandler
|
|
||||||
import com.jessebrault.di.QualifierHandlerContainer
|
|
||||||
import com.jessebrault.di.RegistryExtension
|
|
||||||
|
|
||||||
import java.lang.annotation.Annotation
|
|
||||||
|
|
||||||
class TextsExtension implements QualifierHandlerContainer, RegistryExtension {
|
|
||||||
|
|
||||||
final Set<Text> allTexts = []
|
|
||||||
|
|
||||||
private final QualifierHandler<InjectText> injectTextQualifierHandler = new InjectTextQualifierHandler(this)
|
|
||||||
private final QualifierHandler<InjectTexts> injectTextsQualifierHandler = new InjectTextsQualifierHandler(this)
|
|
||||||
|
|
||||||
@Override
|
|
||||||
<A extends Annotation> QualifierHandler<A> getQualifierHandler(Class<A> aClass) {
|
|
||||||
if (InjectText.is(aClass)) {
|
|
||||||
return this.injectTextQualifierHandler as QualifierHandler<A>
|
|
||||||
} else if (InjectTexts.is(aClass)) {
|
|
||||||
return this.injectTextsQualifierHandler as QualifierHandler<A>
|
|
||||||
} else {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,56 @@
|
|||||||
|
package com.jessebrault.ssg.dsl
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.part.Part
|
||||||
|
import com.jessebrault.ssg.render.RenderContext
|
||||||
|
import com.jessebrault.ssg.text.Text
|
||||||
|
import com.jessebrault.ssg.util.Diagnostic
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import org.jetbrains.annotations.Nullable
|
||||||
|
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull
|
||||||
|
|
||||||
|
@EqualsAndHashCode(includeFields = true)
|
||||||
|
final class EmbeddablePart {
|
||||||
|
|
||||||
|
private final Part part
|
||||||
|
private final RenderContext context
|
||||||
|
private final Consumer<Collection<Diagnostic>> diagnosticsConsumer
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final Text text
|
||||||
|
|
||||||
|
EmbeddablePart(
|
||||||
|
Part part,
|
||||||
|
RenderContext context,
|
||||||
|
Consumer<Collection<Diagnostic>> diagnosticsConsumer,
|
||||||
|
@Nullable Text text
|
||||||
|
) {
|
||||||
|
this.part = requireNonNull(part)
|
||||||
|
this.context = requireNonNull(context)
|
||||||
|
this.diagnosticsConsumer = requireNonNull(diagnosticsConsumer)
|
||||||
|
this.text = text
|
||||||
|
}
|
||||||
|
|
||||||
|
String render(Map binding = [:]) {
|
||||||
|
def result = this.part.type.renderer.render(
|
||||||
|
this.part,
|
||||||
|
binding,
|
||||||
|
this.context,
|
||||||
|
this.text
|
||||||
|
)
|
||||||
|
if (result.hasDiagnostics()) {
|
||||||
|
this.diagnosticsConsumer.accept(result.diagnostics)
|
||||||
|
''
|
||||||
|
} else {
|
||||||
|
result.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"EmbeddablePart(part: ${ this.part })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package com.jessebrault.ssg.dsl
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.render.RenderContext
|
||||||
|
import com.jessebrault.ssg.text.Text
|
||||||
|
import com.jessebrault.ssg.util.Diagnostic
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import org.jetbrains.annotations.Nullable
|
||||||
|
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
import static java.util.Objects.requireNonNull
|
||||||
|
|
||||||
|
@EqualsAndHashCode(includeFields = true)
|
||||||
|
final class EmbeddablePartsMap {
|
||||||
|
|
||||||
|
@Delegate
|
||||||
|
private final Map<String, EmbeddablePart> partsMap = [:]
|
||||||
|
|
||||||
|
EmbeddablePartsMap(
|
||||||
|
RenderContext context,
|
||||||
|
Consumer<Collection<Diagnostic>> diagnosticsConsumer,
|
||||||
|
@Nullable Text text = null
|
||||||
|
) {
|
||||||
|
requireNonNull(context)
|
||||||
|
requireNonNull(diagnosticsConsumer)
|
||||||
|
context.parts.each {
|
||||||
|
this[it.path] = new EmbeddablePart(it, context, diagnosticsConsumer, text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"EmbeddablePartsMap(partsMap: ${ this.partsMap })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
package com.jessebrault.ssg.dsl
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.text.FrontMatter
|
||||||
|
import com.jessebrault.ssg.text.Text
|
||||||
|
import com.jessebrault.ssg.util.Diagnostic
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.Memoized
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
@TupleConstructor(includeFields = true, defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode(includeFields = true)
|
||||||
|
final class EmbeddableText {
|
||||||
|
|
||||||
|
private final Text text
|
||||||
|
private final Consumer<Collection<Diagnostic>> diagnosticsConsumer
|
||||||
|
|
||||||
|
@Memoized
|
||||||
|
String render() {
|
||||||
|
def result = this.text.type.renderer.render(this.text)
|
||||||
|
if (result.diagnostics.size() > 0) {
|
||||||
|
this.diagnosticsConsumer.accept(result.diagnostics)
|
||||||
|
''
|
||||||
|
} else {
|
||||||
|
result.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Memoized
|
||||||
|
FrontMatter getFrontMatter() {
|
||||||
|
def result = this.text.type.frontMatterGetter.get(this.text)
|
||||||
|
if (result.hasDiagnostics()) {
|
||||||
|
this.diagnosticsConsumer.accept(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.diagnosticsConsumer.accept(result.diagnostics)
|
||||||
|
''
|
||||||
|
} else {
|
||||||
|
result.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String getPath() {
|
||||||
|
this.text.path
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"EmbeddableText(text: ${ this.text })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package com.jessebrault.ssg.dsl
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.text.Text
|
||||||
|
import com.jessebrault.ssg.util.Diagnostic
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
@NullCheck
|
||||||
|
@EqualsAndHashCode(includeFields = true)
|
||||||
|
final class EmbeddableTextsCollection {
|
||||||
|
|
||||||
|
@Delegate
|
||||||
|
private final Collection<EmbeddableText> embeddableTexts = []
|
||||||
|
|
||||||
|
EmbeddableTextsCollection(Collection<Text> texts, Consumer<Collection<Diagnostic>> diagnosticsConsumer) {
|
||||||
|
texts.each {
|
||||||
|
this << new EmbeddableText(it, diagnosticsConsumer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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,81 @@
|
|||||||
|
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 com.jessebrault.ssg.util.Diagnostic
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
import java.util.function.Consumer
|
||||||
|
|
||||||
|
final class StandardDslMap {
|
||||||
|
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
static final class Builder {
|
||||||
|
|
||||||
|
private final Map<String, Object> custom = [:]
|
||||||
|
|
||||||
|
Consumer<Collection<Diagnostic>> diagnosticsConsumer = { }
|
||||||
|
String loggerName = ''
|
||||||
|
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,
|
||||||
|
Consumer<Builder> builderConsumer
|
||||||
|
) {
|
||||||
|
def b = new Builder()
|
||||||
|
builderConsumer.accept(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.diagnosticsConsumer,
|
||||||
|
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.diagnosticsConsumer
|
||||||
|
) : null
|
||||||
|
it.texts = new EmbeddableTextsCollection(
|
||||||
|
context.texts,
|
||||||
|
b.diagnosticsConsumer
|
||||||
|
)
|
||||||
|
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, Object> 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, Object> 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, Object> attributes)
|
||||||
|
String create(String name, String body)
|
||||||
|
String create(String name, Map<String, Object> attributes, String body)
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
package com.jessebrault.ssg.html
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.task.AbstractTask
|
||||||
|
import com.jessebrault.ssg.task.Task
|
||||||
|
import com.jessebrault.ssg.task.TaskInput
|
||||||
|
import com.jessebrault.ssg.util.Diagnostic
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
|
||||||
|
@NullCheck
|
||||||
|
@EqualsAndHashCode
|
||||||
|
abstract class AbstractHtmlTask<I extends TaskInput> extends AbstractTask implements HtmlTask {
|
||||||
|
|
||||||
|
final String htmlPath
|
||||||
|
final I input
|
||||||
|
final HtmlOutput output
|
||||||
|
|
||||||
|
AbstractHtmlTask(
|
||||||
|
String name,
|
||||||
|
String htmlPath,
|
||||||
|
I input,
|
||||||
|
File buildDir
|
||||||
|
) {
|
||||||
|
super(name)
|
||||||
|
this.htmlPath = htmlPath
|
||||||
|
this.input = input
|
||||||
|
this.output = new SimpleHtmlOutput(
|
||||||
|
"htmlOutput:${ htmlPath }",
|
||||||
|
new File(buildDir, htmlPath),
|
||||||
|
htmlPath
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 document = Jsoup.parse(content)
|
||||||
|
document.outputSettings().indentAmount(4)
|
||||||
|
def formatted = document.toString()
|
||||||
|
this.output.file.createParentDirectories()
|
||||||
|
this.output.file.write(formatted)
|
||||||
|
[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"AbstractHtmlTask(path: ${ this.htmlPath }, super: ${ super.toString() })"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.jessebrault.ssg.html
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.task.FileOutput
|
||||||
|
|
||||||
|
interface HtmlOutput extends FileOutput {
|
||||||
|
String getHtmlPath()
|
||||||
|
}
|
12
api/src/main/groovy/com/jessebrault/ssg/html/HtmlTask.groovy
Normal file
12
api/src/main/groovy/com/jessebrault/ssg/html/HtmlTask.groovy
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package com.jessebrault.ssg.html
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.task.Task
|
||||||
|
import com.jessebrault.ssg.task.TaskInput
|
||||||
|
|
||||||
|
interface HtmlTask extends Task {
|
||||||
|
@Deprecated
|
||||||
|
String getHtmlPath()
|
||||||
|
|
||||||
|
TaskInput getInput()
|
||||||
|
HtmlOutput getOutput()
|
||||||
|
}
|
@ -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,71 @@
|
|||||||
|
package com.jessebrault.ssg.html
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.SiteSpec
|
||||||
|
import com.jessebrault.ssg.model.Model
|
||||||
|
import com.jessebrault.ssg.model.ModelInput
|
||||||
|
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<ModelInput<T>> {
|
||||||
|
|
||||||
|
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 relativeHtmlPath,
|
||||||
|
TaskSpec taskSpec,
|
||||||
|
Model<T> model,
|
||||||
|
Template template,
|
||||||
|
Collection<Text> allTexts,
|
||||||
|
Collection<Model<Object>> allModels,
|
||||||
|
Collection<Part> allParts
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
"modelToHtml:${ relativeHtmlPath }",
|
||||||
|
relativeHtmlPath,
|
||||||
|
new ModelInput<>(model.name, model),
|
||||||
|
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.htmlPath,
|
||||||
|
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,68 @@
|
|||||||
|
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.page.PageInput
|
||||||
|
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<PageInput> {
|
||||||
|
|
||||||
|
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 relativeHtmlPath,
|
||||||
|
TaskSpec taskSpec,
|
||||||
|
Page page,
|
||||||
|
Collection<Text> allTexts,
|
||||||
|
Collection<Model<Object>> allModels,
|
||||||
|
Collection<Part> allParts
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
"pageToHtml:${ relativeHtmlPath }",
|
||||||
|
relativeHtmlPath,
|
||||||
|
new PageInput(page.path, page),
|
||||||
|
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.htmlPath,
|
||||||
|
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,15 @@
|
|||||||
|
package com.jessebrault.ssg.html
|
||||||
|
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.PackageScope
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@TupleConstructor(defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class SimpleHtmlOutput implements HtmlOutput {
|
||||||
|
final String name
|
||||||
|
final File file
|
||||||
|
final String htmlPath
|
||||||
|
}
|
@ -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,36 @@
|
|||||||
|
package com.jessebrault.ssg.html
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.buildscript.SourceProviders
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProvider
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProviders
|
||||||
|
import com.jessebrault.ssg.util.ExtensionUtil
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
|
||||||
|
final class TextToHtmlSpecProviders {
|
||||||
|
|
||||||
|
static CollectionProvider<Result<TextToHtmlSpec>> from(SourceProviders sources) {
|
||||||
|
CollectionProviders.fromSupplier {
|
||||||
|
def templates = sources.templatesProvider.provide()
|
||||||
|
sources.textsProvider.provide().findResults {
|
||||||
|
def frontMatterResult = it.type.frontMatterGetter.get(it)
|
||||||
|
if (frontMatterResult.hasDiagnostics()) {
|
||||||
|
return Result.ofDiagnostics(frontMatterResult.diagnostics) as Result<TextToHtmlSpec>
|
||||||
|
}
|
||||||
|
def templateValue = frontMatterResult.get().get('template')
|
||||||
|
if (templateValue) {
|
||||||
|
def template = templates.find { it.path == templateValue }
|
||||||
|
return Result.of(new TextToHtmlSpec(
|
||||||
|
it,
|
||||||
|
template,
|
||||||
|
ExtensionUtil.stripExtension(it.path) + '.html'
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextToHtmlSpecProviders() {}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,71 @@
|
|||||||
|
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.text.TextInput
|
||||||
|
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<TextInput> {
|
||||||
|
|
||||||
|
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 relativeHtmlPath,
|
||||||
|
TaskSpec taskSpec,
|
||||||
|
Text text,
|
||||||
|
Template template,
|
||||||
|
Collection<Text> allTexts,
|
||||||
|
Collection<Model<Object>> allModels,
|
||||||
|
Collection<Part> allParts
|
||||||
|
) {
|
||||||
|
super(
|
||||||
|
"textToHtml:${ relativeHtmlPath }",
|
||||||
|
relativeHtmlPath,
|
||||||
|
new TextInput(text.path, text),
|
||||||
|
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.htmlPath,
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,6 +2,5 @@ package com.jessebrault.ssg.model
|
|||||||
|
|
||||||
interface Model<T> {
|
interface Model<T> {
|
||||||
String getName()
|
String getName()
|
||||||
Class<T> getType()
|
|
||||||
T get()
|
T get()
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.jessebrault.ssg.model
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.task.TaskInput
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@TupleConstructor(defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class ModelInput<T> implements TaskInput {
|
||||||
|
final String name
|
||||||
|
final Model<T> model
|
||||||
|
}
|
@ -1,27 +1,15 @@
|
|||||||
package com.jessebrault.ssg.model
|
package com.jessebrault.ssg.model
|
||||||
|
|
||||||
import com.jessebrault.fp.provider.NamedProvider
|
|
||||||
import com.jessebrault.fp.provider.Provider
|
|
||||||
|
|
||||||
import java.util.function.Supplier
|
import java.util.function.Supplier
|
||||||
|
|
||||||
final class Models {
|
final class Models {
|
||||||
|
|
||||||
@SuppressWarnings('GroovyAssignabilityCheck')
|
|
||||||
static <T> Model<T> of(String name, T t) {
|
static <T> Model<T> of(String name, T t) {
|
||||||
new SimpleModel<>(name, t.class, t)
|
new SimpleModel<>(name, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
static <T> Model<T> ofSupplier(String name, Class<T> type, Supplier<? extends T> tClosure) {
|
static <T> Model<T> fromSupplier(String name, Supplier<T> tClosure) {
|
||||||
new SupplierBasedModel<>(name, type, tClosure)
|
new SupplierBasedModel<>(name, tClosure)
|
||||||
}
|
|
||||||
|
|
||||||
static <T> Model<T> ofProvider(String name, Provider<? extends T> modelProvider) {
|
|
||||||
new ProviderModel<T>(name, modelProvider.type, modelProvider)
|
|
||||||
}
|
|
||||||
|
|
||||||
static <T> Model<T> ofNamedProvider(NamedProvider<? extends T> namedModelProvider) {
|
|
||||||
new ProviderModel<T>(namedModelProvider.name, namedModelProvider.type, namedModelProvider)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Models() {}
|
private Models() {}
|
||||||
|
@ -12,7 +12,6 @@ import groovy.transform.TupleConstructor
|
|||||||
final class SimpleModel<T> implements Model<T> {
|
final class SimpleModel<T> implements Model<T> {
|
||||||
|
|
||||||
final String name
|
final String name
|
||||||
final Class<T> type
|
|
||||||
private final T t
|
private final T t
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -22,7 +21,7 @@ final class SimpleModel<T> implements Model<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
String toString() {
|
String toString() {
|
||||||
"SimpleModel(name: $name, type: $type.name)"
|
"SimpleModel(${ this.t })"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -14,17 +14,11 @@ import java.util.function.Supplier
|
|||||||
final class SupplierBasedModel<T> implements Model<T> {
|
final class SupplierBasedModel<T> implements Model<T> {
|
||||||
|
|
||||||
final String name
|
final String name
|
||||||
final Class<T> type
|
private final Supplier<T> supplier
|
||||||
|
|
||||||
private final Supplier<? extends T> supplier
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
T get() {
|
T get() {
|
||||||
this.supplier.get()
|
this.supplier.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
String toString() {
|
|
||||||
"SupplierBasedModel(name: $name, type: $type.name)"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
33
api/src/main/groovy/com/jessebrault/ssg/mutable/Mutable.java
Normal file
33
api/src/main/groovy/com/jessebrault/ssg/mutable/Mutable.java
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package com.jessebrault.ssg.mutable;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.*;
|
||||||
|
|
||||||
|
@ApiStatus.Experimental
|
||||||
|
public interface Mutable<T> {
|
||||||
|
T get();
|
||||||
|
void set(T t);
|
||||||
|
void unset();
|
||||||
|
|
||||||
|
boolean isPresent();
|
||||||
|
|
||||||
|
default boolean isEmpty() {
|
||||||
|
return !this.isPresent();
|
||||||
|
}
|
||||||
|
|
||||||
|
void filterInPlace(Predicate<T> filter);
|
||||||
|
void mapInPlace(UnaryOperator<T> mapper);
|
||||||
|
<U> void zipInPlace(Mutable<U> other, Supplier<T> onEmpty, Supplier<U> onOtherEmpty, BiFunction<T, U, T> zipper);
|
||||||
|
|
||||||
|
<U> U match(Supplier<U> onEmpty, Function<T, U> onPresentMapper);
|
||||||
|
T getOrElse(Supplier<T> onEmpty);
|
||||||
|
|
||||||
|
<U> Mutable<U> chain(Function<T, Mutable<U>> mapper);
|
||||||
|
<U> Mutable<U> map(Function<T, U> mapper);
|
||||||
|
|
||||||
|
<U, R> Mutable<R> zip(Mutable<U> other, BiFunction<T, U, R> zipper);
|
||||||
|
|
||||||
|
Optional<T> asOptional();
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
package com.jessebrault.ssg.mutable;
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.util.Monoid;
|
||||||
|
import com.jessebrault.ssg.util.Monoids;
|
||||||
|
import com.jessebrault.ssg.util.Semigroup;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
@ApiStatus.Experimental
|
||||||
|
public final class Mutables {
|
||||||
|
|
||||||
|
public static <T> Monoid<Mutable<T>> getMonoid(final Semigroup<T> tSemigroup) {
|
||||||
|
return Monoids.of(Mutables.getEmpty(), (m0, m1) -> {
|
||||||
|
if (m0.isPresent() && m1.isPresent()) {
|
||||||
|
return get(tSemigroup.getConcat().apply(m0.get(), m1.get()));
|
||||||
|
} else if (m0.isPresent()) {
|
||||||
|
return m0;
|
||||||
|
} else if (m1.isPresent()) {
|
||||||
|
return m1;
|
||||||
|
} else {
|
||||||
|
return getEmpty();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> @NotNull Mutable<T> getEmpty() {
|
||||||
|
return new SimpleMutable<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> Mutable<T> get(T initialValue) {
|
||||||
|
return new SimpleMutable<>(initialValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mutables() {}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
package com.jessebrault.ssg.mutable;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.*;
|
||||||
|
|
||||||
|
@ApiStatus.Experimental
|
||||||
|
final class SimpleMutable<T> implements Mutable<T> {
|
||||||
|
|
||||||
|
private T t;
|
||||||
|
|
||||||
|
public SimpleMutable(T initialValue) {
|
||||||
|
this.t = initialValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleMutable() {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T get() {
|
||||||
|
if (this.t == null) {
|
||||||
|
throw new NullPointerException();
|
||||||
|
}
|
||||||
|
return this.t;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void set(T t) {
|
||||||
|
this.t = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unset() {
|
||||||
|
this.t = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPresent() {
|
||||||
|
return this.t != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void filterInPlace(Predicate<T> filter) {
|
||||||
|
if (this.t != null && !filter.test(this.t)) {
|
||||||
|
this.unset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void mapInPlace(UnaryOperator<T> mapper) {
|
||||||
|
this.t = mapper.apply(this.t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <U> void zipInPlace(Mutable<U> other, Supplier<T> onEmpty, Supplier<U> onOtherEmpty, BiFunction<T, U, T> zipper) {
|
||||||
|
this.t = zipper.apply(
|
||||||
|
this.isPresent() ? this.t : onEmpty.get(),
|
||||||
|
other.isPresent() ? other.get() : onOtherEmpty.get()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <U> U match(Supplier<U> onEmpty, Function<T, U> onPresentMapper) {
|
||||||
|
return this.t != null ? onPresentMapper.apply(this.t) : onEmpty.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T getOrElse(Supplier<T> other) {
|
||||||
|
return this.t != null ? this.t : other.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <U> Mutable<U> chain(Function<T, Mutable<U>> mapper) {
|
||||||
|
return this.map(mapper).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <U> Mutable<U> map(Function<T, U> mapper) {
|
||||||
|
return this.t != null ? Mutables.get(mapper.apply(this.t)) : Mutables.getEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <U, R> Mutable<R> zip(Mutable<U> other, BiFunction<T, U, R> zipper) {
|
||||||
|
return this.isPresent() && other.isPresent()
|
||||||
|
? Mutables.get(zipper.apply(this.get(), other.get()))
|
||||||
|
: Mutables.getEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<T> asOptional() {
|
||||||
|
return Optional.ofNullable(this.t);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,33 +0,0 @@
|
|||||||
package com.jessebrault.ssg.page
|
|
||||||
|
|
||||||
abstract class AbstractPage implements Page {
|
|
||||||
|
|
||||||
final String name
|
|
||||||
final String path
|
|
||||||
final String fileExtension
|
|
||||||
|
|
||||||
AbstractPage(Map args) {
|
|
||||||
name = args.name
|
|
||||||
path = args.path
|
|
||||||
fileExtension = args.fileExtension
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
int hashCode() {
|
|
||||||
Objects.hash(name, path, fileExtension)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
boolean equals(Object obj) {
|
|
||||||
if (this.is(obj)) {
|
|
||||||
return true
|
|
||||||
} else if (obj instanceof Page) {
|
|
||||||
return name == obj.name
|
|
||||||
&& path == obj.path
|
|
||||||
&& fileExtension == obj.fileExtension
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,76 +0,0 @@
|
|||||||
package com.jessebrault.ssg.page
|
|
||||||
|
|
||||||
import com.jessebrault.ssg.di.SelfPageExtension
|
|
||||||
import com.jessebrault.ssg.util.Diagnostic
|
|
||||||
import com.jessebrault.ssg.view.PageView
|
|
||||||
import com.jessebrault.ssg.view.WvcCompiler
|
|
||||||
import com.jessebrault.ssg.view.WvcPageView
|
|
||||||
import com.jessebrault.di.RegistryObjectFactory
|
|
||||||
import com.jessebrault.fp.either.Either
|
|
||||||
|
|
||||||
class DefaultWvcPage extends AbstractPage implements Page {
|
|
||||||
|
|
||||||
final Class<? extends WvcPageView> viewType
|
|
||||||
final String templateResource
|
|
||||||
final RegistryObjectFactory objectFactory
|
|
||||||
final WvcCompiler wvcCompiler
|
|
||||||
|
|
||||||
DefaultWvcPage(Map args) {
|
|
||||||
super(args)
|
|
||||||
viewType = args.viewType
|
|
||||||
templateResource = args.templateResource ?: viewType.simpleName + 'Template.wvc'
|
|
||||||
objectFactory = args.objectFactory
|
|
||||||
wvcCompiler = args.wvcCompiler
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
Either<Diagnostic, PageView> createView() {
|
|
||||||
WvcPageView pageView
|
|
||||||
try {
|
|
||||||
objectFactory.registry.getExtension(SelfPageExtension).currentPage = this
|
|
||||||
pageView = objectFactory.createInstance(viewType)
|
|
||||||
} catch (Exception exception) {
|
|
||||||
return Either.left(new Diagnostic(
|
|
||||||
"There was an exception while constructing $viewType.name for $name",
|
|
||||||
exception
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pageView.componentTemplate == null) {
|
|
||||||
def compileResult = wvcCompiler.compileTemplate(viewType, templateResource)
|
|
||||||
if (compileResult.isRight()) {
|
|
||||||
pageView.componentTemplate = compileResult.getRight()
|
|
||||||
} else {
|
|
||||||
return Either.left(compileResult.getLeft())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Either.right(pageView)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
int hashCode() {
|
|
||||||
Objects.hash(name, path, fileExtension, viewType, templateResource, objectFactory, wvcCompiler)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
boolean equals(Object obj) {
|
|
||||||
if (!super.equals(obj)) {
|
|
||||||
return false
|
|
||||||
} else if (obj instanceof DefaultWvcPage) {
|
|
||||||
return viewType == obj.viewType
|
|
||||||
&& templateResource == obj.templateResource
|
|
||||||
&& objectFactory == obj.objectFactory
|
|
||||||
&& wvcCompiler == obj.wvcCompiler
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
String toString() {
|
|
||||||
"DefaultPage(name: $name, path: $path, fileExtension: $fileExtension, " +
|
|
||||||
"viewType: $viewType, templateResource: $templateResource)"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,44 @@
|
|||||||
|
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.diagnosticsConsumer = diagnostics.&addAll
|
||||||
|
it.loggerName = "GspSpecialPage(${ specialPage.path })"
|
||||||
|
}
|
||||||
|
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()"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,15 +1,21 @@
|
|||||||
package com.jessebrault.ssg.page
|
package com.jessebrault.ssg.page
|
||||||
|
|
||||||
import com.jessebrault.ssg.util.Diagnostic
|
import groovy.transform.EqualsAndHashCode
|
||||||
import com.jessebrault.ssg.view.PageView
|
import groovy.transform.NullCheck
|
||||||
import com.jessebrault.fp.either.Either
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
interface Page {
|
@TupleConstructor(defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class Page {
|
||||||
|
|
||||||
String getName()
|
final String path
|
||||||
String getPath()
|
final PageType type
|
||||||
String getFileExtension()
|
final String text
|
||||||
|
|
||||||
Either<Diagnostic, PageView> createView()
|
@Override
|
||||||
|
String toString() {
|
||||||
|
"Page(path: ${ this.path }, type: ${ this.type })"
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
package com.jessebrault.ssg.page
|
|
||||||
|
|
||||||
interface PageFactory {
|
|
||||||
Collection<Page> create()
|
|
||||||
}
|
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.jessebrault.ssg.page
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.task.TaskInput
|
||||||
|
import groovy.transform.EqualsAndHashCode
|
||||||
|
import groovy.transform.NullCheck
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
@TupleConstructor(defaults = false)
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode
|
||||||
|
final class PageInput implements TaskInput {
|
||||||
|
final String name
|
||||||
|
final Page page
|
||||||
|
}
|
@ -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
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
@ -1,15 +0,0 @@
|
|||||||
package com.jessebrault.ssg.page
|
|
||||||
|
|
||||||
import java.lang.annotation.ElementType
|
|
||||||
import java.lang.annotation.Retention
|
|
||||||
import java.lang.annotation.RetentionPolicy
|
|
||||||
import java.lang.annotation.Target
|
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Target(ElementType.TYPE)
|
|
||||||
@interface PageSpec {
|
|
||||||
String name()
|
|
||||||
String path()
|
|
||||||
String templateResource() default ''
|
|
||||||
String fileExtension() default '.html'
|
|
||||||
}
|
|
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,33 @@
|
|||||||
|
package com.jessebrault.ssg.page
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProvider
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProviders
|
||||||
|
import com.jessebrault.ssg.util.ExtensionUtil
|
||||||
|
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.fromDirectory(pagesDirectory) { file, relativePath ->
|
||||||
|
def extension = ExtensionUtil.getExtension(relativePath)
|
||||||
|
if (extension) {
|
||||||
|
def pageType = pageTypes.find { it.ids.contains(extension) }
|
||||||
|
if (!pageType) {
|
||||||
|
logger.debug('there is no PageType for file {}; skipping', file)
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
return new Page(relativePath, pageType, file.getText())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debug('there is no extension for file {}; skipping', file)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PagesProviders() {}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
package com.jessebrault.ssg.part
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.render.RenderContext
|
||||||
|
import com.jessebrault.ssg.render.StandardGspRenderer
|
||||||
|
import com.jessebrault.ssg.text.Text
|
||||||
|
import com.jessebrault.ssg.util.Diagnostic
|
||||||
|
import com.jessebrault.ssg.util.Result
|
||||||
|
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.diagnosticsConsumer = diagnostics.&addAll
|
||||||
|
it.loggerName = "GspPart(${ part.path })"
|
||||||
|
if (text) {
|
||||||
|
it.text = text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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,33 @@
|
|||||||
|
package com.jessebrault.ssg.part
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProvider
|
||||||
|
import com.jessebrault.ssg.provider.CollectionProviders
|
||||||
|
import com.jessebrault.ssg.util.ExtensionUtil
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
final class PartsProviders {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(PartsProviders)
|
||||||
|
|
||||||
|
static CollectionProvider<Part> from(File partsDir, Collection<PartType> partTypes) {
|
||||||
|
CollectionProviders.fromDirectory(partsDir) { file, relativePath ->
|
||||||
|
def extension = ExtensionUtil.getExtension(relativePath)
|
||||||
|
if (extension) {
|
||||||
|
def partType = partTypes.find { it.ids.contains(extension) }
|
||||||
|
if (!partType) {
|
||||||
|
logger.debug('there is no PartType for file {}; skipping', file)
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
return new Part(relativePath, partType, file.getText())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debug('there is no extension for file {}; skipping', file)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PartsProviders() {}
|
||||||
|
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user