Compare commits
77 Commits
v0.2.0-SNA
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e97381687a | ||
![]() |
1826b9bc58 | ||
![]() |
90a2769fb7 | ||
![]() |
aa44f0550d | ||
![]() |
5b53676a82 | ||
![]() |
ad2b24754f | ||
![]() |
6ef7fb0117 | ||
![]() |
807556cd73 | ||
![]() |
ab5b678920 | ||
![]() |
82a8be76b5 | ||
![]() |
4757a2e9a5 | ||
![]() |
3170d2b2e9 | ||
![]() |
57fa73f4c3 | ||
![]() |
1799db2e37 | ||
![]() |
753f3b67b1 | ||
![]() |
894bd55033 | ||
![]() |
8355ec548b | ||
![]() |
dfc9324a23 | ||
![]() |
779cd26753 | ||
![]() |
ae625d1229 | ||
![]() |
0d7544f202 | ||
![]() |
57dd965153 | ||
![]() |
4930034ae2 | ||
![]() |
640dc1f856 | ||
![]() |
022b4a018f | ||
![]() |
a233395cc7 | ||
![]() |
760b734f21 | ||
![]() |
c384bf15e9 | ||
![]() |
b4906b1066 | ||
![]() |
78bec0dd53 | ||
![]() |
599fb919c7 | ||
![]() |
8ae16e327f | ||
![]() |
150a2d71cb | ||
![]() |
1ae3ef43bb | ||
![]() |
f9f5bf5889 | ||
![]() |
cfafaf0df9 | ||
![]() |
8967338fae | ||
![]() |
52145cf013 | ||
![]() |
3c363cb71c | ||
![]() |
b5a7b1f67d | ||
![]() |
31a6c79929 | ||
![]() |
d32ac97caf | ||
![]() |
d9f8cae0ea | ||
![]() |
0998d1a11d | ||
![]() |
5975f5110b | ||
![]() |
13b22b1afa | ||
![]() |
a221980d98 | ||
![]() |
d1c8a74355 | ||
![]() |
adc96982f2 | ||
![]() |
69552922c1 | ||
![]() |
526f01c683 | ||
![]() |
30e463f1cf | ||
![]() |
e34bb38350 | ||
![]() |
bc1d545297 | ||
![]() |
140dffefc6 | ||
![]() |
76c6280b5d | ||
![]() |
c502727243 | ||
![]() |
f11334c74b | ||
![]() |
02180cc522 | ||
![]() |
597310f031 | ||
![]() |
96d5d59df7 | ||
![]() |
103d10e8c4 | ||
![]() |
aa7194fa89 | ||
![]() |
5514208735 | ||
![]() |
40cfacf646 | ||
![]() |
0e260a45a1 | ||
![]() |
0a230775b9 | ||
![]() |
3a1ecfe524 | ||
![]() |
c7ba01380e | ||
![]() |
f3c6f1ef3c | ||
![]() |
c18438ff6a | ||
![]() |
f44f2df797 | ||
![]() |
b741765b24 | ||
![]() |
2208c9f4c0 | ||
![]() |
956642339c | ||
![]() |
bc28a00cfc | ||
![]() |
f5697fb99b |
28
.gitea/workflows/check-publish-release.yml
Normal file
28
.gitea/workflows/check-publish-release.yml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
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
29
.github/workflows/release.yml
vendored
@ -1,29 +0,0 @@
|
|||||||
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
Normal file
16
README.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
## Future
|
## 0.6.0
|
||||||
|
- [ ] Plugin system for build scripts
|
||||||
|
|
||||||
### Add
|
## 0.5.0
|
||||||
- [ ] Plan out plugin system such that we can create custom providers of texts, data, etc.
|
- [ ] watch/dev mode and server
|
||||||
- [ ] Add `Watchable` interface/trait back; an abstraction over FS watching and other sources (such as a database, etc.).
|
- [ ] Reorganize gradle project layout so there is less hunting around for files
|
||||||
- [ ] Explore `apply(Plugin)` in buildScripts.
|
|
||||||
|
|
||||||
### Fix
|
## 0.4.* Ongoing
|
||||||
|
- [ ] 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
|
||||||
|
|
||||||
## v0.2.0
|
## 0.4.3
|
||||||
|
- [ ] `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.
|
||||||
|
|
||||||
### Add
|
## 0.4.1
|
||||||
- [ ] Write manual.
|
- [x] Update groowt to 0.1.2.
|
||||||
- [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,28 +1,58 @@
|
|||||||
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 {
|
||||||
// https://mvnrepository.com/artifact/org.apache.groovy/groovy-templates
|
api libs.groovy
|
||||||
implementation 'org.apache.groovy:groovy-templates:4.0.12'
|
api libs.groovy.yaml
|
||||||
|
|
||||||
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
api libs.groowt.v
|
||||||
implementation 'org.commonmark:commonmark:0.21.0'
|
api libs.groowt.vc
|
||||||
|
api libs.groowt.wvc
|
||||||
|
api libs.di
|
||||||
|
api libs.fp
|
||||||
|
|
||||||
// https://mvnrepository.com/artifact/org.commonmark/commonmark-ext-yaml-front-matter
|
compileOnlyApi libs.jetbrains.anontations
|
||||||
implementation 'org.commonmark:commonmark-ext-yaml-front-matter:0.21.0'
|
|
||||||
|
|
||||||
// https://mvnrepository.com/artifact/org.jsoup/jsoup
|
implementation libs.classgraph
|
||||||
implementation 'org.jsoup:jsoup:1.16.1'
|
implementation libs.commonmark
|
||||||
|
implementation libs.commonmark.frontmatter
|
||||||
|
implementation libs.jsoup
|
||||||
|
|
||||||
// https://mvnrepository.com/artifact/org.jgrapht/jgrapht-core
|
runtimeOnly libs.groowt.wvcc
|
||||||
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,106 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package com.jessebrault.ssg
|
|
||||||
|
|
||||||
import com.jessebrault.ssg.buildscript.Build
|
|
||||||
import com.jessebrault.ssg.task.Task
|
|
||||||
import com.jessebrault.ssg.util.Result
|
|
||||||
|
|
||||||
interface BuildTasksConverter {
|
|
||||||
Result<Collection<Task>> convert(Build buildScriptResult)
|
|
||||||
}
|
|
@ -0,0 +1,322 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,33 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
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 })"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
13
api/src/main/groovy/com/jessebrault/ssg/SsgException.groovy
Normal file
13
api/src/main/groovy/com/jessebrault/ssg/SsgException.groovy
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package com.jessebrault.ssg
|
||||||
|
|
||||||
|
class SsgException extends RuntimeException {
|
||||||
|
|
||||||
|
SsgException(String message) {
|
||||||
|
super(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
SsgException(String message, Throwable cause) {
|
||||||
|
super(message, cause)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,8 +2,11 @@ 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 {
|
||||||
boolean doBuild(String buildName, Consumer<Collection<Diagnostic>> diagnosticsConsumer)
|
Collection<Diagnostic> doBuild(
|
||||||
|
File projectDir,
|
||||||
|
String buildName,
|
||||||
|
String buildScriptFqn,
|
||||||
|
Map<String, String> buildScriptCliArgs
|
||||||
|
)
|
||||||
}
|
}
|
@ -1,57 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,79 +0,0 @@
|
|||||||
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,7 +1,8 @@
|
|||||||
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 groovy.transform.PackageScope
|
import org.jetbrains.annotations.ApiStatus
|
||||||
|
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
|
||||||
@ -9,55 +10,67 @@ 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 {
|
||||||
|
|
||||||
protected static final Logger logger = LoggerFactory.getLogger(BuildScriptBase)
|
/* --- Logging --- */
|
||||||
protected static final Marker enter = MarkerFactory.getMarker('ENTER')
|
|
||||||
protected static final Marker exit = MarkerFactory.getMarker('EXIT')
|
|
||||||
|
|
||||||
protected final Collection<BuildSpec> buildSpecs = []
|
static final Logger logger = LoggerFactory.getLogger(BuildScriptBase)
|
||||||
|
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
|
||||||
* @param args
|
private Closure buildClosure = { }
|
||||||
* @param buildClosure
|
private File projectRoot
|
||||||
*/
|
private String buildName
|
||||||
void abstractBuild(
|
|
||||||
Map<String, Object> args,
|
/* --- Instance DSL helpers --- */
|
||||||
@DelegatesTo(value = BuildDelegate, strategy = Closure.DELEGATE_FIRST)
|
|
||||||
Closure<?> buildClosure
|
File file(String name) {
|
||||||
) {
|
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) {
|
||||||
* @param args
|
this.extending = extending
|
||||||
* @param buildClosure
|
this.buildClosure = 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
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PackageScope
|
void build(@DelegatesTo(value = BuildDelegate) Closure buildClosure) {
|
||||||
Collection<BuildSpec> getBuildSpecs() {
|
this.extending = null
|
||||||
this.buildSpecs
|
this.buildClosure = buildClosure
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
package com.jessebrault.ssg.buildscript
|
|
||||||
|
|
||||||
import java.util.function.Consumer
|
|
||||||
|
|
||||||
interface BuildScriptConfiguratorFactory {
|
|
||||||
Consumer<BuildScriptBase> get()
|
|
||||||
}
|
|
@ -0,0 +1,30 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,79 +0,0 @@
|
|||||||
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,50 +1,48 @@
|
|||||||
package com.jessebrault.ssg.buildscript
|
package com.jessebrault.ssg.buildscript
|
||||||
|
|
||||||
import com.jessebrault.ssg.buildscript.delegates.BuildDelegate
|
import com.jessebrault.ssg.model.Model
|
||||||
|
import com.jessebrault.ssg.text.TextConverter
|
||||||
import groovy.transform.EqualsAndHashCode
|
import groovy.transform.EqualsAndHashCode
|
||||||
import groovy.transform.NullCheck
|
import groovy.transform.NullCheck
|
||||||
import groovy.transform.PackageScope
|
import com.jessebrault.di.RegistryObjectFactory
|
||||||
import groovy.transform.TupleConstructor
|
import com.jessebrault.fp.provider.Provider
|
||||||
|
|
||||||
@PackageScope
|
import static com.jessebrault.ssg.util.ObjectUtil.requireProvider
|
||||||
@NullCheck()
|
import static com.jessebrault.ssg.util.ObjectUtil.requireString
|
||||||
@EqualsAndHashCode(excludes = 'buildClosure')
|
|
||||||
|
@NullCheck(includeGenerated = true)
|
||||||
|
@EqualsAndHashCode
|
||||||
final class BuildSpec {
|
final class BuildSpec {
|
||||||
|
|
||||||
static BuildSpec getEmpty() {
|
|
||||||
new BuildSpec('', false, BuildExtension.getEmpty(), { })
|
|
||||||
}
|
|
||||||
|
|
||||||
static BuildSpec get(Map<String, Object> args) {
|
|
||||||
new BuildSpec(
|
|
||||||
args.name as String ?: '',
|
|
||||||
args.isAbstract as boolean ?: false,
|
|
||||||
args.extending as BuildExtension ?: BuildExtension.getEmpty(),
|
|
||||||
args.buildClosure as Closure<?> ?: { }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
final String name
|
final String name
|
||||||
final boolean isAbstract
|
final Provider<Set<String>> basePackages
|
||||||
final BuildExtension extending
|
final Provider<String> siteName
|
||||||
final Closure<?> buildClosure
|
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
|
||||||
|
|
||||||
BuildSpec(
|
@SuppressWarnings('GroovyAssignabilityCheck')
|
||||||
String name,
|
BuildSpec(Map args) {
|
||||||
boolean isAbstract,
|
this.name = requireString(args.name)
|
||||||
BuildExtension extending,
|
this.basePackages = requireProvider(args.basePackages)
|
||||||
@DelegatesTo(value = BuildDelegate, strategy = Closure.DELEGATE_FIRST)
|
this.siteName = requireProvider(args.siteName)
|
||||||
Closure<?> buildClosure
|
this.baseUrl = requireProvider(args.baseUrl)
|
||||||
) {
|
this.outputDir = requireProvider(args.outputDir)
|
||||||
this.name = name
|
this.globals = requireProvider(args.globals)
|
||||||
this.isAbstract = isAbstract
|
this.models = requireProvider(args.models)
|
||||||
this.extending = extending
|
this.textsDirs = requireProvider(args.textsDirs)
|
||||||
this.buildClosure = buildClosure
|
this.textConverters = requireProvider(args.textConverters)
|
||||||
|
this.objectFactoryBuilder = requireProvider(args.objectFactoryBuilder)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
String toString() {
|
String toString() {
|
||||||
"BuildSpec(name: ${ this.name }, isAbstract: ${ this.isAbstract }, extending: ${ this.extending })"
|
"Build(name: ${this.name}, basePackages: $basePackages, siteName: $siteName, " +
|
||||||
|
"baseUrl: $baseUrl, outputDir: $outputDir, textsDirs: $textsDirs)"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,105 +0,0 @@
|
|||||||
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() {}
|
|
||||||
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
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 })"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
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() {}
|
|
||||||
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
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 })"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,57 +0,0 @@
|
|||||||
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,287 +1,132 @@
|
|||||||
package com.jessebrault.ssg.buildscript.delegates
|
package com.jessebrault.ssg.buildscript.delegates
|
||||||
|
|
||||||
import com.jessebrault.ssg.SiteSpec
|
import com.jessebrault.ssg.model.Model
|
||||||
import com.jessebrault.ssg.buildscript.Build
|
import com.jessebrault.ssg.model.Models
|
||||||
import com.jessebrault.ssg.buildscript.OutputDir
|
import com.jessebrault.ssg.text.MarkdownTextConverter
|
||||||
import com.jessebrault.ssg.buildscript.SourceProviders
|
import com.jessebrault.ssg.text.TextConverter
|
||||||
import com.jessebrault.ssg.buildscript.TypesContainer
|
import com.jessebrault.ssg.util.PathUtil
|
||||||
import com.jessebrault.ssg.mutable.Mutable
|
import com.jessebrault.di.DefaultRegistryObjectFactory
|
||||||
import com.jessebrault.ssg.mutable.Mutables
|
import com.jessebrault.di.RegistryObjectFactory
|
||||||
import com.jessebrault.ssg.task.TaskFactory
|
import com.jessebrault.fp.property.DefaultProperty
|
||||||
import com.jessebrault.ssg.task.TaskFactorySpec
|
import com.jessebrault.fp.property.Property
|
||||||
import com.jessebrault.ssg.util.Monoid
|
import com.jessebrault.fp.provider.DefaultProvider
|
||||||
import groovy.transform.EqualsAndHashCode
|
import com.jessebrault.fp.provider.NamedProvider
|
||||||
import groovy.transform.NullCheck
|
import com.jessebrault.fp.provider.Provider
|
||||||
import groovy.transform.TupleConstructor
|
|
||||||
import groovy.transform.stc.ClosureParams
|
|
||||||
import groovy.transform.stc.FromString
|
|
||||||
import groovy.transform.stc.SimpleType
|
|
||||||
|
|
||||||
import java.util.function.Function
|
import java.nio.file.Path
|
||||||
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 {
|
||||||
|
|
||||||
@TupleConstructor(includeFields = true, defaults = false)
|
static BuildDelegate withDefaults(String buildName, File projectDir) {
|
||||||
@NullCheck(includeGenerated = true)
|
new BuildDelegate(projectDir).tap {
|
||||||
@EqualsAndHashCode(includeFields = true)
|
basePackages.convention = [] as Set<String>
|
||||||
static final class Results {
|
outputDir.convention = PathUtil.resolve(projectDir, Path.of('dist', buildName.split(/\\./)))
|
||||||
|
globals.convention = [:]
|
||||||
private final BuildDelegate delegate
|
models.convention = [] as Set<Model>
|
||||||
|
textsDirs.convention = [new File(projectDir, 'texts')] as Set<File>
|
||||||
Function<Build, OutputDir> getOutputDirFunctionResult(
|
textConverters.convention = [new MarkdownTextConverter()] as Set<TextConverter>
|
||||||
Function<Build, OutputDir> base,
|
objectFactoryBuilder.convention = DefaultRegistryObjectFactory.Builder.withDefaults()
|
||||||
Supplier<Function<Build, OutputDir>> onEmpty
|
|
||||||
) {
|
|
||||||
this.delegate.outputDirFunction.getOrElse {
|
|
||||||
this.delegate.outputDirFunctionMapper.match(onEmpty) {
|
|
||||||
it.apply(base)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SiteSpec getSiteSpecResult(
|
|
||||||
SiteSpec base,
|
|
||||||
boolean onConcatWithBaseEmpty,
|
|
||||||
Monoid<SiteSpec> siteSpecMonoid
|
|
||||||
) {
|
|
||||||
def concatWithBase = this.delegate.siteSpecConcatBase.isPresent()
|
|
||||||
? this.delegate.siteSpecConcatBase.get()
|
|
||||||
: onConcatWithBaseEmpty
|
|
||||||
def onEmpty = { concatWithBase ? base : siteSpecMonoid.zero }
|
|
||||||
this.delegate.siteSpecClosure.match(onEmpty) {
|
|
||||||
def d = new SiteSpecDelegate(siteSpecMonoid)
|
|
||||||
it.delegate = d
|
|
||||||
//noinspection UnnecessaryQualifiedReference
|
|
||||||
it.resolveStrategy = Closure.DELEGATE_FIRST
|
|
||||||
it(base)
|
|
||||||
def r = d.getResult()
|
|
||||||
concatWithBase ? siteSpecMonoid.concat.apply(base, r) : r
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Object> getGlobalsResult(
|
final File projectDir
|
||||||
Map<String, Object> base,
|
|
||||||
boolean onConcatWithBaseEmpty,
|
final Property<Set<String>> basePackages = DefaultProperty.<Set<String>>empty(Set)
|
||||||
Monoid<Map<String, Object>> globalsMonoid
|
final Property<String> siteName = DefaultProperty.empty(String)
|
||||||
) {
|
final Property<String> baseUrl = DefaultProperty.empty(String)
|
||||||
def concatWithBase = this.delegate.globalsConcatBase.isPresent()
|
final Property<File> outputDir = DefaultProperty.empty(File)
|
||||||
? this.delegate.globalsConcatBase.get()
|
final Property<Map<String, Object>> globals = DefaultProperty.<Map<String, Object>>empty(Map)
|
||||||
: onConcatWithBaseEmpty
|
final Property<Set<Model>> models = DefaultProperty.<Set<Model>>empty(Set)
|
||||||
def onEmpty = { concatWithBase ? base : globalsMonoid.zero }
|
final Property<Set<File>> textsDirs = DefaultProperty.<Set<File>>empty(Set)
|
||||||
this.delegate.globalsClosure.match(onEmpty) {
|
final Property<Set<TextConverter>> textConverters = DefaultProperty.<Set<TextConverter>>empty(Set)
|
||||||
def d = new GlobalsDelegate()
|
final Property<RegistryObjectFactory.Builder> objectFactoryBuilder =
|
||||||
it.delegate = d
|
DefaultProperty.empty(RegistryObjectFactory.Builder)
|
||||||
//noinspection UnnecessaryQualifiedReference
|
|
||||||
it.resolveStrategy = Closure.DELEGATE_FIRST
|
private BuildDelegate(File projectDir) {
|
||||||
it(base)
|
this.projectDir = projectDir
|
||||||
def r = d.getResult()
|
|
||||||
concatWithBase ? globalsMonoid.concat.apply(base, r) : r
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TypesContainer getTypesResult(
|
/* TODO: add friendly DSL methods for setting all properties */
|
||||||
TypesContainer base,
|
|
||||||
boolean onConcatWithBaseEmpty,
|
void basePackage(String toAdd) {
|
||||||
Monoid<TypesContainer> typesContainerMonoid
|
this.basePackages.configure { it.add(toAdd) }
|
||||||
) {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SourceProviders getSourcesResult(
|
void basePackages(String... toAdd) {
|
||||||
SourceProviders base,
|
toAdd.each { this.basePackage(it) }
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Collection<TaskFactorySpec<TaskFactory>> getTaskFactoriesResult(
|
void siteName(String siteName) {
|
||||||
Collection<TaskFactorySpec<TaskFactory>> base,
|
this.siteName.set(siteName)
|
||||||
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 siteName(Provider<String> siteNameProvider) {
|
||||||
|
this.siteName.set(siteNameProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
private final Mutable<Function<Build, OutputDir>> outputDirFunction = Mutables.getEmpty()
|
void baseUrl(String baseUrl) {
|
||||||
private final Mutable<UnaryOperator<Function<Build, OutputDir>>> outputDirFunctionMapper = Mutables.getEmpty()
|
this.baseUrl.set(baseUrl)
|
||||||
|
|
||||||
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 setOutputDir(File file) {
|
void baseUrl(Provider<String> baseUrlProvider) {
|
||||||
this.outputDirFunction.set { new OutputDir(file) }
|
this.baseUrl.set(baseUrlProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
void setOutputDir(String path) {
|
void outputDir(File outputDir) {
|
||||||
this.outputDirFunction.set { new OutputDir(path) }
|
this.outputDir.set(outputDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maps the *base*
|
void outputDir(Provider<File> outputDirProvider) {
|
||||||
void outputDirFunction(UnaryOperator<Function<Build, OutputDir>> mapper) {
|
this.outputDir.set(outputDirProvider)
|
||||||
this.outputDirFunctionMapper.set(mapper)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void siteSpec(
|
void globals(@DelegatesTo(value = GlobalsDelegate, strategy = Closure.DELEGATE_FIRST) Closure globalsClosure) {
|
||||||
@DelegatesTo(value = SiteSpecDelegate, strategy = Closure.DELEGATE_FIRST)
|
def globalsDelegate = new GlobalsDelegate()
|
||||||
@ClosureParams(value = SimpleType, options = 'com.jessebrault.ssg.SiteSpec')
|
globalsClosure.delegate = globalsDelegate
|
||||||
Closure<?> siteSpecClosure
|
globalsClosure.resolveStrategy = Closure.DELEGATE_FIRST
|
||||||
) {
|
globalsClosure()
|
||||||
this.siteSpec(true, siteSpecClosure)
|
this.globals.set this.globals.get() + globalsDelegate
|
||||||
}
|
}
|
||||||
|
|
||||||
void siteSpec(
|
void model(String name, Object obj) {
|
||||||
boolean concatWithBase,
|
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.siteSpecConcatBase.set(concatWithBase)
|
|
||||||
this.siteSpecClosure.set(siteSpecClosure)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void globals(
|
void model(Model model) {
|
||||||
@DelegatesTo(value = GlobalsDelegate, strategy = Closure.DELEGATE_FIRST)
|
this.models.configure { it.add(model) }
|
||||||
@ClosureParams(value = FromString, options = 'Map<String, Object>')
|
|
||||||
Closure<?> globalsClosure
|
|
||||||
) {
|
|
||||||
this.globals(true, globalsClosure)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void globals(
|
void model(String name, Provider tProvider) {
|
||||||
boolean concatWithBase,
|
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.globalsConcatBase.set(concatWithBase)
|
|
||||||
this.globalsClosure.set(globalsClosure)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void types(
|
<T> void model(String name, Class<T> type, Supplier<? extends T> tSupplier) {
|
||||||
@DelegatesTo(value = TypesDelegate, strategy = Closure.DELEGATE_FIRST)
|
this.models.configure { it.add(Models.ofSupplier(name, type, tSupplier)) }
|
||||||
@ClosureParams(value = SimpleType, options = 'com.jessebrault.ssg.buildscript.TypesContainer')
|
|
||||||
Closure<?> typesClosure
|
|
||||||
) {
|
|
||||||
this.types(true, typesClosure)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void types(
|
void model(NamedProvider namedProvider) {
|
||||||
boolean concatWithBase,
|
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.typesConcatBase.set(concatWithBase)
|
|
||||||
this.typesClosure.set(typesClosure)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void sources(
|
void textsDir(File textsDir) {
|
||||||
@DelegatesTo(value = SourceProvidersDelegate, strategy = Closure.DELEGATE_FIRST)
|
this.textsDirs.configure { it.add(textsDir) }
|
||||||
@ClosureParams(
|
|
||||||
value = FromString,
|
|
||||||
options = 'com.jessebrault.ssg.buildscript.SourceProviders, com.jessebrault.ssg.buildscript.TypesContainer'
|
|
||||||
)
|
|
||||||
Closure<?> sourcesClosure
|
|
||||||
) {
|
|
||||||
this.sources(true, sourcesClosure)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void sources(
|
void textsDirs(File... textsDirs) {
|
||||||
boolean concatWithBase,
|
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.sourcesConcatBase.set(concatWithBase)
|
|
||||||
this.sourcesClosure.set(sourcesClosure)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void taskFactories(
|
void textConverter(TextConverter textConverter) {
|
||||||
@DelegatesTo(value = TaskFactoriesDelegate, strategy = Closure.DELEGATE_FIRST)
|
this.textConverters.configure { it.add(textConverter) }
|
||||||
@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 taskFactories(
|
void textConverters(TextConverter... textConverters) {
|
||||||
boolean concatWithBase,
|
textConverters.each { this.textConverter(it) }
|
||||||
@DelegatesTo(value = TaskFactoriesDelegate, strategy = Closure.DELEGATE_FIRST)
|
}
|
||||||
@ClosureParams(
|
|
||||||
value = FromString,
|
void objectFactoryBuilder(RegistryObjectFactory.Builder builder) {
|
||||||
options = 'java.util.Collection<com.jessebrault.ssg.task.TaskFactorySpec<com.jessebrault.ssg.task.TaskFactory>>, com.jessebrault.ssg.buildscript.SourceProviders'
|
this.objectFactoryBuilder.set(builder)
|
||||||
)
|
|
||||||
Closure<?> taskFactoriesClosure
|
|
||||||
) {
|
|
||||||
this.taskFactoriesConcatBase.set(concatWithBase)
|
|
||||||
this.taskFactoriesClosure.set(taskFactoriesClosure)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
15
api/src/main/groovy/com/jessebrault/ssg/di/Global.groovy
Normal file
15
api/src/main/groovy/com/jessebrault/ssg/di/Global.groovy
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
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()
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
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()
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
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()
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
20
api/src/main/groovy/com/jessebrault/ssg/di/InjectPage.groovy
Normal file
20
api/src/main/groovy/com/jessebrault/ssg/di/InjectPage.groovy
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
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()
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
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()
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
20
api/src/main/groovy/com/jessebrault/ssg/di/InjectText.groovy
Normal file
20
api/src/main/groovy/com/jessebrault/ssg/di/InjectText.groovy
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
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()
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
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()
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
13
api/src/main/groovy/com/jessebrault/ssg/di/SelfPage.groovy
Normal file
13
api/src/main/groovy/com/jessebrault/ssg/di/SelfPage.groovy
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
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 {}
|
@ -0,0 +1,42 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,229 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
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() {}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,56 +0,0 @@
|
|||||||
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 })"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
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 })"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
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 })"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
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 })"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,81 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
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>
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,77 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
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() })"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
package com.jessebrault.ssg.html
|
|
||||||
|
|
||||||
import com.jessebrault.ssg.task.FileOutput
|
|
||||||
|
|
||||||
interface HtmlOutput extends FileOutput {
|
|
||||||
String getHtmlPath()
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
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() })"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
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
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,68 +0,0 @@
|
|||||||
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() })"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
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() {}
|
|
||||||
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
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() })"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
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,5 +2,6 @@ package com.jessebrault.ssg.model
|
|||||||
|
|
||||||
interface Model<T> {
|
interface Model<T> {
|
||||||
String getName()
|
String getName()
|
||||||
|
Class<T> getType()
|
||||||
T get()
|
T get()
|
||||||
}
|
}
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
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,15 +1,27 @@
|
|||||||
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)
|
new SimpleModel<>(name, t.class, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
static <T> Model<T> fromSupplier(String name, Supplier<T> tClosure) {
|
static <T> Model<T> ofSupplier(String name, Class<T> type, Supplier<? extends T> tClosure) {
|
||||||
new SupplierBasedModel<>(name, tClosure)
|
new SupplierBasedModel<>(name, type, 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() {}
|
||||||
|
@ -1,28 +1,28 @@
|
|||||||
package com.jessebrault.ssg.provider
|
package com.jessebrault.ssg.model
|
||||||
|
|
||||||
import groovy.transform.EqualsAndHashCode
|
import groovy.transform.EqualsAndHashCode
|
||||||
import groovy.transform.NullCheck
|
import groovy.transform.NullCheck
|
||||||
import groovy.transform.PackageScope
|
import groovy.transform.PackageScope
|
||||||
import groovy.transform.TupleConstructor
|
import groovy.transform.TupleConstructor
|
||||||
|
import com.jessebrault.fp.provider.Provider
|
||||||
import java.util.function.Supplier
|
|
||||||
|
|
||||||
@PackageScope
|
@PackageScope
|
||||||
@TupleConstructor(includeFields = true, defaults = false)
|
@TupleConstructor(includeFields = true, defaults = false)
|
||||||
@NullCheck(includeGenerated = true)
|
@NullCheck(includeGenerated = true)
|
||||||
@EqualsAndHashCode(includeFields = true)
|
@EqualsAndHashCode
|
||||||
final class SupplierBasedProvider<T> extends AbstractProvider<T> {
|
class ProviderModel<T> implements Model<T> {
|
||||||
|
|
||||||
private final Supplier<T> supplier
|
final String name
|
||||||
|
final Class<T> type
|
||||||
|
private final Provider<T> modelProvider
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
T provide() {
|
T get() {
|
||||||
this.supplier.get()
|
this.modelProvider.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
String toString() {
|
String toString() {
|
||||||
"SupplierBasedProvider(supplier: ${ this.supplier })"
|
"ProviderModel(name: $name, type: $type)"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -12,6 +12,7 @@ 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
|
||||||
@ -21,7 +22,7 @@ final class SimpleModel<T> implements Model<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
String toString() {
|
String toString() {
|
||||||
"SimpleModel(${ this.t })"
|
"SimpleModel(name: $name, type: $type.name)"
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -14,11 +14,17 @@ 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
|
||||||
private final Supplier<T> supplier
|
final Class<T> type
|
||||||
|
|
||||||
|
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)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
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() {}
|
|
||||||
|
|
||||||
}
|
|
@ -1,94 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,33 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
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)"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,44 +0,0 @@
|
|||||||
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,21 +1,15 @@
|
|||||||
package com.jessebrault.ssg.page
|
package com.jessebrault.ssg.page
|
||||||
|
|
||||||
import groovy.transform.EqualsAndHashCode
|
import com.jessebrault.ssg.util.Diagnostic
|
||||||
import groovy.transform.NullCheck
|
import com.jessebrault.ssg.view.PageView
|
||||||
import groovy.transform.TupleConstructor
|
import com.jessebrault.fp.either.Either
|
||||||
|
|
||||||
@TupleConstructor(defaults = false)
|
interface Page {
|
||||||
@NullCheck(includeGenerated = true)
|
|
||||||
@EqualsAndHashCode
|
|
||||||
final class Page {
|
|
||||||
|
|
||||||
final String path
|
String getName()
|
||||||
final PageType type
|
String getPath()
|
||||||
final String text
|
String getFileExtension()
|
||||||
|
|
||||||
@Override
|
Either<Diagnostic, PageView> createView()
|
||||||
String toString() {
|
|
||||||
"Page(path: ${ this.path }, type: ${ this.type })"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
package com.jessebrault.ssg.page
|
||||||
|
|
||||||
|
interface PageFactory {
|
||||||
|
Collection<Page> create()
|
||||||
|
}
|
@ -1,14 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
15
api/src/main/groovy/com/jessebrault/ssg/page/PageSpec.groovy
Normal file
15
api/src/main/groovy/com/jessebrault/ssg/page/PageSpec.groovy
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
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'
|
||||||
|
}
|
@ -1,20 +0,0 @@
|
|||||||
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 })"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package com.jessebrault.ssg.page
|
|
||||||
|
|
||||||
final class PageTypes {
|
|
||||||
|
|
||||||
static final PageType GSP = new PageType(['.gsp'], new GspPageRenderer())
|
|
||||||
|
|
||||||
private PageTypes() {}
|
|
||||||
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
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() {}
|
|
||||||
|
|
||||||
}
|
|
@ -1,55 +0,0 @@
|
|||||||
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()"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
package com.jessebrault.ssg.part
|
|
||||||
|
|
||||||
import groovy.transform.EqualsAndHashCode
|
|
||||||
import groovy.transform.NullCheck
|
|
||||||
import groovy.transform.TupleConstructor
|
|
||||||
|
|
||||||
@TupleConstructor(defaults = false)
|
|
||||||
@NullCheck(includeGenerated = true)
|
|
||||||
@EqualsAndHashCode
|
|
||||||
final class Part {
|
|
||||||
|
|
||||||
String path
|
|
||||||
PartType type
|
|
||||||
String text
|
|
||||||
|
|
||||||
@Override
|
|
||||||
String toString() {
|
|
||||||
"Part(path: ${ this.path }, type: ${ this.type })"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package com.jessebrault.ssg.part
|
|
||||||
|
|
||||||
import groovy.transform.EqualsAndHashCode
|
|
||||||
import groovy.transform.NullCheck
|
|
||||||
import groovy.transform.TupleConstructor
|
|
||||||
|
|
||||||
@TupleConstructor(defaults = false)
|
|
||||||
@NullCheck(includeGenerated = true)
|
|
||||||
@EqualsAndHashCode
|
|
||||||
final class PartType {
|
|
||||||
|
|
||||||
Collection<String> ids
|
|
||||||
PartRenderer renderer
|
|
||||||
|
|
||||||
@Override
|
|
||||||
String toString() {
|
|
||||||
"PartType(ids: ${ this.ids }, renderer: ${ this.renderer })"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
package com.jessebrault.ssg.part
|
|
||||||
|
|
||||||
final class PartTypes {
|
|
||||||
|
|
||||||
static final PartType GSP = new PartType(['.gsp'], new GspPartRenderer())
|
|
||||||
|
|
||||||
private PartTypes() {}
|
|
||||||
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user