Compare commits
128 Commits
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 | ||
![]() |
9cd303d317 | ||
![]() |
fa58875f88 | ||
![]() |
89f4d37b10 | ||
![]() |
cf29843371 | ||
![]() |
ab0b09fb62 | ||
![]() |
5eaa32f536 | ||
![]() |
594ed8ba7d | ||
![]() |
d73f2ba521 | ||
![]() |
cdda27ea3a | ||
![]() |
450e5ca428 | ||
![]() |
369f9da53d | ||
![]() |
1f8bd3270c | ||
![]() |
c74d80a69e | ||
![]() |
4fa2ba0ac9 | ||
![]() |
2a4d3d8025 | ||
![]() |
3b181307a1 | ||
![]() |
7204b1b694 | ||
![]() |
dfcc2364e3 | ||
![]() |
375cb5444c | ||
![]() |
4c920fb485 | ||
![]() |
5a70f9c91c | ||
![]() |
7708ac66e0 | ||
![]() |
2dbbe53a10 | ||
![]() |
7ec9107165 | ||
![]() |
f5f5bf9f6c | ||
![]() |
f611aa3227 | ||
![]() |
3803b26c04 | ||
![]() |
9559b53003 | ||
![]() |
ced050b793 | ||
![]() |
334fa655dd | ||
![]() |
bec9dcb234 | ||
![]() |
3d74a79088 | ||
![]() |
8fd08f51f1 | ||
![]() |
3e276232c3 | ||
![]() |
93dac553a4 | ||
![]() |
07228050cb | ||
![]() |
fdc2c83e99 | ||
![]() |
95f86629f3 | ||
![]() |
cca5b0c1d4 | ||
![]() |
958d3ca0ff | ||
![]() |
2af6eeddec | ||
![]() |
9b743c52a4 | ||
![]() |
2ce269bbdc | ||
![]() |
a8c6955ca8 | ||
![]() |
306b4bb8d3 | ||
![]() |
44d4baeb62 | ||
![]() |
6cae49ca14 | ||
![]() |
b1488f7434 | ||
![]() |
accf9d6d05 | ||
![]() |
8b5bf93822 | ||
![]() |
db5d00bdca |
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 19
|
||||
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.
|
49
TODO.md
49
TODO.md
@ -1,11 +1,51 @@
|
||||
# TODO
|
||||
|
||||
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.
|
||||
|
||||
## Next
|
||||
## 0.6.0
|
||||
- [ ] Plugin system for build scripts
|
||||
|
||||
### Add
|
||||
- [ ] Add some kind of `outputs` map to dsl that can be used to retrieve various info about another output of the current build. For example:
|
||||
## 0.5.0
|
||||
- [ ] watch/dev mode and server
|
||||
- [ ] Reorganize gradle project layout so there is less hunting around for files
|
||||
|
||||
## 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
|
||||
|
||||
## 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.
|
||||
|
||||
## 0.4.1
|
||||
- [x] Update groowt to 0.1.2.
|
||||
|
||||
### v0.2.0
|
||||
- [x] Investigate imports, including static, in scripts
|
||||
- Does not work; must use binding
|
||||
- [x] Get rid of `taskTypes` DSL, replace with static import of task types to scripts
|
||||
- Done via the binding directly
|
||||
- [x] Plan out `data` models DSL
|
||||
- Done via `models` dsl
|
||||
|
||||
### v0.1.0
|
||||
- [x] Add some kind of `outputs` map to dsl that can be used to retrieve various info about another output of the current build. For example:
|
||||
```groovy
|
||||
// while in a special page 'special.gsp' we could get the 'output' info for a text 'blog/post.md'
|
||||
def post = outputs['blog/post.md']
|
||||
@ -14,6 +54,3 @@ Here will be kept all of the various todos for this project, organized by releas
|
||||
assert post.targetPath = 'blog/post.html'
|
||||
// as well as some other information, perhaps such as the Type, extension, *etc.*
|
||||
```
|
||||
- [ ] Add `extensionUtil` object to dsl.
|
||||
|
||||
### Fix
|
||||
|
58
api/build.gradle
Normal file
58
api/build.gradle
Normal file
@ -0,0 +1,58 @@
|
||||
plugins {
|
||||
id 'ssg-common'
|
||||
id 'groovy'
|
||||
id 'java-library'
|
||||
id 'java-test-fixtures'
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
configurations {
|
||||
testFixturesApi {
|
||||
extendsFrom configurations.testing
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api libs.groovy
|
||||
api libs.groovy.yaml
|
||||
|
||||
api libs.groowt.v
|
||||
api libs.groowt.vc
|
||||
api libs.groowt.wvc
|
||||
api libs.di
|
||||
api libs.fp
|
||||
|
||||
compileOnlyApi libs.jetbrains.anontations
|
||||
|
||||
implementation libs.classgraph
|
||||
implementation libs.commonmark
|
||||
implementation libs.commonmark.frontmatter
|
||||
implementation libs.jsoup
|
||||
|
||||
runtimeOnly libs.groowt.wvcc
|
||||
}
|
||||
|
||||
java {
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
jar {
|
||||
archivesBaseName = 'ssg-api'
|
||||
}
|
||||
|
||||
sourcesJar {
|
||||
archiveBaseName = 'ssg-api'
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
create('ssgApi', MavenPublication) {
|
||||
artifactId = 'api'
|
||||
from components.java
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
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)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
package com.jessebrault.ssg
|
||||
|
||||
import com.jessebrault.ssg.util.Diagnostic
|
||||
|
||||
interface StaticSiteGenerator {
|
||||
Collection<Diagnostic> doBuild(
|
||||
File projectDir,
|
||||
String buildName,
|
||||
String buildScriptFqn,
|
||||
Map<String, String> buildScriptCliArgs
|
||||
)
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package com.jessebrault.ssg.buildscript
|
||||
|
||||
import com.jessebrault.ssg.buildscript.delegates.BuildDelegate
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.Nullable
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.slf4j.Marker
|
||||
import org.slf4j.MarkerFactory
|
||||
|
||||
import static java.util.Objects.requireNonNull
|
||||
|
||||
@SuppressWarnings('unused')
|
||||
abstract class BuildScriptBase extends Script {
|
||||
|
||||
/* --- Logging --- */
|
||||
|
||||
static final Logger logger = LoggerFactory.getLogger(BuildScriptBase)
|
||||
static final Marker enter = MarkerFactory.getMarker('ENTER')
|
||||
static final Marker exit = MarkerFactory.getMarker('EXIT')
|
||||
|
||||
/* --- build script proper --- */
|
||||
|
||||
private String extending
|
||||
private Closure buildClosure = { }
|
||||
private File projectRoot
|
||||
private String buildName
|
||||
|
||||
/* --- Instance DSL helpers --- */
|
||||
|
||||
File file(String name) {
|
||||
new File(this.projectRoot, name)
|
||||
}
|
||||
|
||||
/* --- DSL --- */
|
||||
|
||||
void build(@Nullable String extending, @DelegatesTo(value = BuildDelegate) Closure buildClosure) {
|
||||
this.extending = extending
|
||||
this.buildClosure = buildClosure
|
||||
}
|
||||
|
||||
void build(@DelegatesTo(value = BuildDelegate) Closure buildClosure) {
|
||||
this.extending = null
|
||||
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
|
||||
}
|
||||
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package com.jessebrault.ssg.buildscript
|
||||
|
||||
import com.jessebrault.ssg.model.Model
|
||||
import com.jessebrault.ssg.text.TextConverter
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import groovy.transform.NullCheck
|
||||
import com.jessebrault.di.RegistryObjectFactory
|
||||
import com.jessebrault.fp.provider.Provider
|
||||
|
||||
import static com.jessebrault.ssg.util.ObjectUtil.requireProvider
|
||||
import static com.jessebrault.ssg.util.ObjectUtil.requireString
|
||||
|
||||
@NullCheck(includeGenerated = true)
|
||||
@EqualsAndHashCode
|
||||
final class BuildSpec {
|
||||
|
||||
final String name
|
||||
final Provider<Set<String>> basePackages
|
||||
final Provider<String> siteName
|
||||
final Provider<String> baseUrl
|
||||
final Provider<File> outputDir
|
||||
final Provider<Map<String, Object>> globals
|
||||
final Provider<Set<Model>> models
|
||||
final Provider<Set<File>> textsDirs
|
||||
final Provider<Set<TextConverter>> textConverters
|
||||
final Provider<RegistryObjectFactory.Builder> objectFactoryBuilder
|
||||
|
||||
@SuppressWarnings('GroovyAssignabilityCheck')
|
||||
BuildSpec(Map args) {
|
||||
this.name = requireString(args.name)
|
||||
this.basePackages = requireProvider(args.basePackages)
|
||||
this.siteName = requireProvider(args.siteName)
|
||||
this.baseUrl = requireProvider(args.baseUrl)
|
||||
this.outputDir = requireProvider(args.outputDir)
|
||||
this.globals = requireProvider(args.globals)
|
||||
this.models = requireProvider(args.models)
|
||||
this.textsDirs = requireProvider(args.textsDirs)
|
||||
this.textConverters = requireProvider(args.textConverters)
|
||||
this.objectFactoryBuilder = requireProvider(args.objectFactoryBuilder)
|
||||
}
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
"Build(name: ${this.name}, basePackages: $basePackages, siteName: $siteName, " +
|
||||
"baseUrl: $baseUrl, outputDir: $outputDir, textsDirs: $textsDirs)"
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
package com.jessebrault.ssg.buildscript.delegates
|
||||
|
||||
import com.jessebrault.ssg.model.Model
|
||||
import com.jessebrault.ssg.model.Models
|
||||
import com.jessebrault.ssg.text.MarkdownTextConverter
|
||||
import com.jessebrault.ssg.text.TextConverter
|
||||
import com.jessebrault.ssg.util.PathUtil
|
||||
import com.jessebrault.di.DefaultRegistryObjectFactory
|
||||
import com.jessebrault.di.RegistryObjectFactory
|
||||
import com.jessebrault.fp.property.DefaultProperty
|
||||
import com.jessebrault.fp.property.Property
|
||||
import com.jessebrault.fp.provider.DefaultProvider
|
||||
import com.jessebrault.fp.provider.NamedProvider
|
||||
import com.jessebrault.fp.provider.Provider
|
||||
|
||||
import java.nio.file.Path
|
||||
import java.util.function.Supplier
|
||||
|
||||
final class BuildDelegate {
|
||||
|
||||
static BuildDelegate withDefaults(String buildName, File projectDir) {
|
||||
new BuildDelegate(projectDir).tap {
|
||||
basePackages.convention = [] as Set<String>
|
||||
outputDir.convention = PathUtil.resolve(projectDir, Path.of('dist', buildName.split(/\\./)))
|
||||
globals.convention = [:]
|
||||
models.convention = [] as Set<Model>
|
||||
textsDirs.convention = [new File(projectDir, 'texts')] as Set<File>
|
||||
textConverters.convention = [new MarkdownTextConverter()] as Set<TextConverter>
|
||||
objectFactoryBuilder.convention = DefaultRegistryObjectFactory.Builder.withDefaults()
|
||||
}
|
||||
}
|
||||
|
||||
final File projectDir
|
||||
|
||||
final Property<Set<String>> basePackages = DefaultProperty.<Set<String>>empty(Set)
|
||||
final Property<String> siteName = DefaultProperty.empty(String)
|
||||
final Property<String> baseUrl = DefaultProperty.empty(String)
|
||||
final Property<File> outputDir = DefaultProperty.empty(File)
|
||||
final Property<Map<String, Object>> globals = DefaultProperty.<Map<String, Object>>empty(Map)
|
||||
final Property<Set<Model>> models = DefaultProperty.<Set<Model>>empty(Set)
|
||||
final Property<Set<File>> textsDirs = DefaultProperty.<Set<File>>empty(Set)
|
||||
final Property<Set<TextConverter>> textConverters = DefaultProperty.<Set<TextConverter>>empty(Set)
|
||||
final Property<RegistryObjectFactory.Builder> objectFactoryBuilder =
|
||||
DefaultProperty.empty(RegistryObjectFactory.Builder)
|
||||
|
||||
private BuildDelegate(File projectDir) {
|
||||
this.projectDir = projectDir
|
||||
}
|
||||
|
||||
/* TODO: add friendly DSL methods for setting all properties */
|
||||
|
||||
void basePackage(String toAdd) {
|
||||
this.basePackages.configure { it.add(toAdd) }
|
||||
}
|
||||
|
||||
void basePackages(String... toAdd) {
|
||||
toAdd.each { this.basePackage(it) }
|
||||
}
|
||||
|
||||
void siteName(String siteName) {
|
||||
this.siteName.set(siteName)
|
||||
}
|
||||
|
||||
void siteName(Provider<String> siteNameProvider) {
|
||||
this.siteName.set(siteNameProvider)
|
||||
}
|
||||
|
||||
void baseUrl(String baseUrl) {
|
||||
this.baseUrl.set(baseUrl)
|
||||
}
|
||||
|
||||
void baseUrl(Provider<String> baseUrlProvider) {
|
||||
this.baseUrl.set(baseUrlProvider)
|
||||
}
|
||||
|
||||
void outputDir(File outputDir) {
|
||||
this.outputDir.set(outputDir)
|
||||
}
|
||||
|
||||
void outputDir(Provider<File> outputDirProvider) {
|
||||
this.outputDir.set(outputDirProvider)
|
||||
}
|
||||
|
||||
void globals(@DelegatesTo(value = GlobalsDelegate, strategy = Closure.DELEGATE_FIRST) Closure globalsClosure) {
|
||||
def globalsDelegate = new GlobalsDelegate()
|
||||
globalsClosure.delegate = globalsDelegate
|
||||
globalsClosure.resolveStrategy = Closure.DELEGATE_FIRST
|
||||
globalsClosure()
|
||||
this.globals.set this.globals.get() + globalsDelegate
|
||||
}
|
||||
|
||||
void model(String name, Object obj) {
|
||||
this.models.configure {it.add(Models.of(name, obj)) }
|
||||
}
|
||||
|
||||
void model(Model model) {
|
||||
this.models.configure { it.add(model) }
|
||||
}
|
||||
|
||||
void model(String name, Provider tProvider) {
|
||||
this.models.configure { it.add(Models.ofProvider(name, tProvider)) }
|
||||
}
|
||||
|
||||
<T> void model(String name, Class<T> type, Supplier<? extends T> tSupplier) {
|
||||
this.models.configure { it.add(Models.ofSupplier(name, type, tSupplier)) }
|
||||
}
|
||||
|
||||
void model(NamedProvider namedProvider) {
|
||||
this.models.configure { it.add(Models.ofNamedProvider(namedProvider)) }
|
||||
}
|
||||
|
||||
void textsDir(File textsDir) {
|
||||
this.textsDirs.configure { it.add(textsDir) }
|
||||
}
|
||||
|
||||
void textsDirs(File... textsDirs) {
|
||||
textsDirs.each { this.textsDir(it) }
|
||||
}
|
||||
|
||||
void textConverter(TextConverter textConverter) {
|
||||
this.textConverters.configure { it.add(textConverter) }
|
||||
}
|
||||
|
||||
void textConverters(TextConverter... textConverters) {
|
||||
textConverters.each { this.textConverter(it) }
|
||||
}
|
||||
|
||||
void objectFactoryBuilder(RegistryObjectFactory.Builder builder) {
|
||||
this.objectFactoryBuilder.set(builder)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package com.jessebrault.ssg.buildscript.delegates
|
||||
|
||||
final class GlobalsDelegate {
|
||||
|
||||
@Delegate
|
||||
final Map<String, Object> globals = [:]
|
||||
|
||||
@Override
|
||||
Object getProperty(String propertyName) {
|
||||
this.globals[propertyName]
|
||||
}
|
||||
|
||||
@Override
|
||||
void setProperty(String propertyName, Object newValue) {
|
||||
this.globals[propertyName] = newValue
|
||||
}
|
||||
|
||||
Map<String, Object> getResult() {
|
||||
this.globals
|
||||
}
|
||||
|
||||
}
|
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,8 +1,8 @@
|
||||
package com.jessebrault.ssg.url
|
||||
package com.jessebrault.ssg.dsl.urlbuilder
|
||||
|
||||
import java.nio.file.Path
|
||||
|
||||
class PathBasedUrlBuilder implements UrlBuilder {
|
||||
final class PathBasedUrlBuilder implements UrlBuilder {
|
||||
|
||||
private final String absolute
|
||||
private final String baseUrl
|
@ -1,4 +1,4 @@
|
||||
package com.jessebrault.ssg.url
|
||||
package com.jessebrault.ssg.dsl.urlbuilder
|
||||
|
||||
interface UrlBuilder {
|
||||
String getAbsolute()
|
@ -0,0 +1,7 @@
|
||||
package com.jessebrault.ssg.model
|
||||
|
||||
interface Model<T> {
|
||||
String getName()
|
||||
Class<T> getType()
|
||||
T get()
|
||||
}
|
29
api/src/main/groovy/com/jessebrault/ssg/model/Models.groovy
Normal file
29
api/src/main/groovy/com/jessebrault/ssg/model/Models.groovy
Normal file
@ -0,0 +1,29 @@
|
||||
package com.jessebrault.ssg.model
|
||||
|
||||
import com.jessebrault.fp.provider.NamedProvider
|
||||
import com.jessebrault.fp.provider.Provider
|
||||
|
||||
import java.util.function.Supplier
|
||||
|
||||
final class Models {
|
||||
|
||||
@SuppressWarnings('GroovyAssignabilityCheck')
|
||||
static <T> Model<T> of(String name, T t) {
|
||||
new SimpleModel<>(name, t.class, t)
|
||||
}
|
||||
|
||||
static <T> Model<T> ofSupplier(String name, Class<T> type, Supplier<? extends T> 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() {}
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.jessebrault.ssg.model
|
||||
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import groovy.transform.NullCheck
|
||||
import groovy.transform.PackageScope
|
||||
import groovy.transform.TupleConstructor
|
||||
import com.jessebrault.fp.provider.Provider
|
||||
|
||||
@PackageScope
|
||||
@TupleConstructor(includeFields = true, defaults = false)
|
||||
@NullCheck(includeGenerated = true)
|
||||
@EqualsAndHashCode
|
||||
class ProviderModel<T> implements Model<T> {
|
||||
|
||||
final String name
|
||||
final Class<T> type
|
||||
private final Provider<T> modelProvider
|
||||
|
||||
@Override
|
||||
T get() {
|
||||
this.modelProvider.get()
|
||||
}
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
"ProviderModel(name: $name, type: $type)"
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
package com.jessebrault.ssg.model
|
||||
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import groovy.transform.NullCheck
|
||||
import groovy.transform.PackageScope
|
||||
import groovy.transform.TupleConstructor
|
||||
|
||||
@PackageScope
|
||||
@TupleConstructor(includeFields = true, defaults = false)
|
||||
@NullCheck(includeGenerated = true)
|
||||
@EqualsAndHashCode(includeFields = true)
|
||||
final class SimpleModel<T> implements Model<T> {
|
||||
|
||||
final String name
|
||||
final Class<T> type
|
||||
private final T t
|
||||
|
||||
@Override
|
||||
T get() {
|
||||
this.t
|
||||
}
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
"SimpleModel(name: $name, type: $type.name)"
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.jessebrault.ssg.model
|
||||
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import groovy.transform.NullCheck
|
||||
import groovy.transform.PackageScope
|
||||
import groovy.transform.TupleConstructor
|
||||
|
||||
import java.util.function.Supplier
|
||||
|
||||
@PackageScope
|
||||
@TupleConstructor(includeFields = true, defaults = false)
|
||||
@NullCheck(includeGenerated = true)
|
||||
@EqualsAndHashCode(includeFields = true)
|
||||
final class SupplierBasedModel<T> implements Model<T> {
|
||||
|
||||
final String name
|
||||
final Class<T> type
|
||||
|
||||
private final Supplier<? extends T> supplier
|
||||
|
||||
@Override
|
||||
T get() {
|
||||
this.supplier.get()
|
||||
}
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
"SupplierBasedModel(name: $name, type: $type.name)"
|
||||
}
|
||||
}
|
@ -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)"
|
||||
}
|
||||
|
||||
}
|
15
api/src/main/groovy/com/jessebrault/ssg/page/Page.groovy
Normal file
15
api/src/main/groovy/com/jessebrault/ssg/page/Page.groovy
Normal file
@ -0,0 +1,15 @@
|
||||
package com.jessebrault.ssg.page
|
||||
|
||||
import com.jessebrault.ssg.util.Diagnostic
|
||||
import com.jessebrault.ssg.view.PageView
|
||||
import com.jessebrault.fp.either.Either
|
||||
|
||||
interface Page {
|
||||
|
||||
String getName()
|
||||
String getPath()
|
||||
String getFileExtension()
|
||||
|
||||
Either<Diagnostic, PageView> createView()
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package com.jessebrault.ssg.page
|
||||
|
||||
interface PageFactory {
|
||||
Collection<Page> create()
|
||||
}
|
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'
|
||||
}
|
132
api/src/main/groovy/com/jessebrault/ssg/text/MarkdownText.groovy
Normal file
132
api/src/main/groovy/com/jessebrault/ssg/text/MarkdownText.groovy
Normal file
@ -0,0 +1,132 @@
|
||||
package com.jessebrault.ssg.text
|
||||
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import groovy.transform.NullCheck
|
||||
import groovy.yaml.YamlSlurper
|
||||
import org.commonmark.ext.front.matter.YamlFrontMatterExtension
|
||||
import org.commonmark.node.AbstractVisitor
|
||||
import org.commonmark.node.Node
|
||||
import org.commonmark.parser.Parser
|
||||
import org.commonmark.renderer.html.HtmlRenderer
|
||||
|
||||
@NullCheck(includeGenerated = true)
|
||||
@EqualsAndHashCode
|
||||
class MarkdownText implements Text {
|
||||
|
||||
protected static class MarkdownExcerptVisitor extends AbstractVisitor {
|
||||
|
||||
private final int length
|
||||
private final List<String> words
|
||||
|
||||
MarkdownExcerptVisitor(int length) {
|
||||
this.length = length
|
||||
this.words = []
|
||||
}
|
||||
|
||||
private int getCurrentLength() {
|
||||
this.words.inject(0) { acc, word -> acc + word.length() }
|
||||
}
|
||||
|
||||
@Override
|
||||
void visit(org.commonmark.node.Text text) {
|
||||
if (this.currentLength < length) {
|
||||
def words = text.literal.split('\\s+').toList()
|
||||
def wordsIter = words.iterator()
|
||||
while (wordsIter.hasNext() && this.currentLength < length) {
|
||||
def word = wordsIter.next()
|
||||
if (word.length() > this.length - this.currentLength) {
|
||||
break
|
||||
} else {
|
||||
//noinspection GroovyVariableNotAssigned -- this is certainly an IntelliJ bug
|
||||
this.words << word
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String getResult() {
|
||||
this.words.join(' ')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final Parser markdownParser = Parser.builder()
|
||||
.extensions(List.of(YamlFrontMatterExtension.create()))
|
||||
.build()
|
||||
|
||||
private static final HtmlRenderer htmlRenderer = HtmlRenderer.builder().build()
|
||||
|
||||
final String name
|
||||
final String path
|
||||
|
||||
private final File source
|
||||
|
||||
private boolean didInit
|
||||
private Object frontMatter
|
||||
private Node parsed
|
||||
|
||||
MarkdownText(String name, String path, File source) {
|
||||
this.name = name
|
||||
this.path = path
|
||||
this.source = source
|
||||
}
|
||||
|
||||
private void initFrontMatter(String completeSourceText) {
|
||||
if (completeSourceText.startsWith('---')) {
|
||||
def delimiterIndex = completeSourceText.indexOf('---', 3)
|
||||
def frontMatter = completeSourceText.substring(3, delimiterIndex)
|
||||
this.frontMatter = new YamlSlurper().parseText(frontMatter)
|
||||
} else {
|
||||
this.frontMatter = [:]
|
||||
}
|
||||
}
|
||||
|
||||
private void initParsed(String completeSourceText) {
|
||||
if (completeSourceText.startsWith('---')) {
|
||||
def delimiterIndex = completeSourceText.indexOf('---', 3)
|
||||
def body = completeSourceText.substring(delimiterIndex + 3)
|
||||
if (body.startsWith('\n')) {
|
||||
this.parsed = markdownParser.parse(body.substring(1))
|
||||
} else {
|
||||
this.parsed = markdownParser.parse(body)
|
||||
}
|
||||
} else {
|
||||
this.parsed = markdownParser.parse(completeSourceText)
|
||||
}
|
||||
}
|
||||
|
||||
private void init() {
|
||||
if (!this.didInit) {
|
||||
def completeSourceText = this.source.text
|
||||
this.initFrontMatter(completeSourceText)
|
||||
this.initParsed(completeSourceText)
|
||||
this.didInit = true
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
Object getFrontMatter() {
|
||||
this.init()
|
||||
this.frontMatter
|
||||
}
|
||||
|
||||
@Override
|
||||
String getExcerpt(int length) {
|
||||
this.init()
|
||||
def v = new MarkdownExcerptVisitor(length)
|
||||
this.parsed.accept(v)
|
||||
v.result
|
||||
}
|
||||
|
||||
@Override
|
||||
void renderTo(Writer writer) {
|
||||
this.init()
|
||||
htmlRenderer.render(this.parsed, writer)
|
||||
}
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
"MarkdownText(name: ${this.name}, path: ${this.path})"
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package com.jessebrault.ssg.text
|
||||
|
||||
import com.jessebrault.ssg.util.ExtensionUtil
|
||||
import com.jessebrault.ssg.util.PathUtil
|
||||
|
||||
class MarkdownTextConverter implements TextConverter {
|
||||
|
||||
private static final Set<String> handledExtensions = ['.md']
|
||||
|
||||
@Override
|
||||
Set<String> getHandledExtensions() {
|
||||
handledExtensions
|
||||
}
|
||||
|
||||
@Override
|
||||
Text convert(File textsDir, File textFile) {
|
||||
new MarkdownText(
|
||||
ExtensionUtil.stripExtension(textFile.name),
|
||||
'/' + PathUtil.relative(textsDir, textFile).toString(),
|
||||
textFile
|
||||
)
|
||||
}
|
||||
|
||||
}
|
18
api/src/main/groovy/com/jessebrault/ssg/text/Text.groovy
Normal file
18
api/src/main/groovy/com/jessebrault/ssg/text/Text.groovy
Normal file
@ -0,0 +1,18 @@
|
||||
package com.jessebrault.ssg.text
|
||||
|
||||
interface Text {
|
||||
|
||||
String getName()
|
||||
String getPath()
|
||||
Object getFrontMatter()
|
||||
String getExcerpt(int length)
|
||||
|
||||
void renderTo(Writer writer)
|
||||
|
||||
default String render() {
|
||||
def w = new StringWriter()
|
||||
this.renderTo(w)
|
||||
w.toString()
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package com.jessebrault.ssg.text
|
||||
|
||||
interface TextConverter {
|
||||
Set<String> getHandledExtensions()
|
||||
Text convert(File textsDir, File textFile)
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.jessebrault.ssg.util
|
||||
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import groovy.transform.TupleConstructor
|
||||
import org.jetbrains.annotations.Nullable
|
||||
|
||||
@TupleConstructor
|
||||
@EqualsAndHashCode
|
||||
final class Diagnostic {
|
||||
|
||||
final String message
|
||||
final @Nullable Exception exception
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
if (this.exception != null) {
|
||||
"Diagnostic(message: $message, exception: $exception.class.name)"
|
||||
} else {
|
||||
"Diagnostic(message: $message)"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
package com.jessebrault.ssg.util
|
||||
|
||||
import org.jetbrains.annotations.Nullable
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class ExtensionsUtil {
|
||||
final class ExtensionUtil {
|
||||
|
||||
private static final Pattern stripExtensionPattern = ~/(.+)\..+$/
|
||||
private static final Pattern getExtensionPattern = ~/.+(\..+)$/
|
||||
@ -12,13 +14,15 @@ class ExtensionsUtil {
|
||||
m.matches() ? m.group(1) : path
|
||||
}
|
||||
|
||||
static String getExtension(String path) {
|
||||
static @Nullable String getExtension(String path) {
|
||||
def m = getExtensionPattern.matcher(path)
|
||||
if (m.matches()) {
|
||||
m.group(1)
|
||||
} else {
|
||||
throw new IllegalArgumentException("cannot get extension for path: ${ path }")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private ExtensionUtil() {}
|
||||
|
||||
}
|
111
api/src/main/groovy/com/jessebrault/ssg/util/Glob.groovy
Normal file
111
api/src/main/groovy/com/jessebrault/ssg/util/Glob.groovy
Normal file
@ -0,0 +1,111 @@
|
||||
package com.jessebrault.ssg.util
|
||||
|
||||
import groovy.transform.TupleConstructor
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
* A very basic class for handling globs. Can handle one ** at most.
|
||||
* In file/directory names, only '.' is escaped; any other special characters
|
||||
* may cause an invalid regex pattern.
|
||||
*/
|
||||
final class Glob {
|
||||
|
||||
private sealed interface GlobPart permits Literal, AnyDirectoryHierarchy, GlobFileOrDirectory {}
|
||||
|
||||
@TupleConstructor
|
||||
private static final class Literal implements GlobPart {
|
||||
final String literal
|
||||
}
|
||||
|
||||
private static final class AnyDirectoryHierarchy implements GlobPart {}
|
||||
|
||||
@TupleConstructor
|
||||
private static final class GlobFileOrDirectory implements GlobPart {
|
||||
final String original
|
||||
final Pattern regexPattern
|
||||
}
|
||||
|
||||
private static List<GlobPart> toParts(String glob) {
|
||||
final List<String> originalParts
|
||||
if (glob.startsWith('/')) {
|
||||
originalParts = glob.substring(1).split('/') as List<String>
|
||||
} else {
|
||||
originalParts = glob.split('/') as List<String>
|
||||
}
|
||||
|
||||
def result = originalParts.collect {
|
||||
if (it == '**') {
|
||||
new AnyDirectoryHierarchy()
|
||||
} else if (it.contains('*')) {
|
||||
def replaced = it.replace([
|
||||
'*': '.*',
|
||||
'.': '\\.'
|
||||
])
|
||||
new GlobFileOrDirectory(it, ~replaced)
|
||||
} else {
|
||||
new Literal(it)
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
private final List<GlobPart> parts
|
||||
|
||||
Glob(String glob) {
|
||||
this.parts = toParts(glob)
|
||||
}
|
||||
|
||||
boolean matches(File file) {
|
||||
this.matches(file.toString().replace(File.separator, '/'))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param subject Must contain only '/' as a separator.
|
||||
* @return whether the subject String matches this glob.
|
||||
*/
|
||||
boolean matches(String subject) {
|
||||
final List<String> subjectParts
|
||||
if (subject.startsWith('/')) {
|
||||
subjectParts = subject.substring(1).split('/') as List<String>
|
||||
} else {
|
||||
subjectParts = subject.split('/') as List<String>
|
||||
}
|
||||
|
||||
boolean result = true
|
||||
parts:
|
||||
for (def part : this.parts) {
|
||||
if (part instanceof Literal) {
|
||||
if (subjectParts.isEmpty()) {
|
||||
result = false
|
||||
break
|
||||
}
|
||||
def subjectPart = subjectParts.removeFirst()
|
||||
if (part.literal != subjectPart) {
|
||||
result = false
|
||||
break
|
||||
}
|
||||
} else if (part instanceof AnyDirectoryHierarchy) {
|
||||
while (!subjectParts.isEmpty()) {
|
||||
def current = subjectParts.removeFirst()
|
||||
if (subjectParts.isEmpty()) {
|
||||
subjectParts.push(current)
|
||||
continue parts
|
||||
}
|
||||
}
|
||||
} else if (part instanceof GlobFileOrDirectory) {
|
||||
def subjectPart = subjectParts.removeFirst()
|
||||
def m = part.regexPattern.matcher(subjectPart)
|
||||
if (!m.matches()) {
|
||||
result = false
|
||||
break parts
|
||||
}
|
||||
} else {
|
||||
throw new IllegalStateException('Should not get here.')
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
package com.jessebrault.ssg.util
|
||||
|
||||
import com.jessebrault.fp.property.Property
|
||||
import com.jessebrault.fp.provider.Provider
|
||||
|
||||
import static java.util.Objects.requireNonNull
|
||||
|
||||
final class ObjectUtil {
|
||||
|
||||
static <T> T requireType(Class<T> type, Object t) {
|
||||
type.cast(requireNonNull(t))
|
||||
}
|
||||
|
||||
static String requireString(s) {
|
||||
requireNonNull(s) as String
|
||||
}
|
||||
|
||||
static File requireFile(f) {
|
||||
requireNonNull(f) as File
|
||||
}
|
||||
|
||||
static Map requireMap(m) {
|
||||
requireNonNull(m) as Map
|
||||
}
|
||||
|
||||
static Set requireSet(s) {
|
||||
requireNonNull(s) as Set
|
||||
}
|
||||
|
||||
static Property requireProperty(p) {
|
||||
requireNonNull(p) as Property
|
||||
}
|
||||
|
||||
static Provider requireProvider(p) {
|
||||
requireNonNull(p) as Provider
|
||||
}
|
||||
|
||||
private ObjectUtil() {}
|
||||
|
||||
}
|
21
api/src/main/groovy/com/jessebrault/ssg/util/PathUtil.groovy
Normal file
21
api/src/main/groovy/com/jessebrault/ssg/util/PathUtil.groovy
Normal file
@ -0,0 +1,21 @@
|
||||
package com.jessebrault.ssg.util
|
||||
|
||||
import java.nio.file.Path
|
||||
|
||||
final class PathUtil {
|
||||
|
||||
static File relative(File base, File target) {
|
||||
base.toPath().relativize(target.toPath()).toFile()
|
||||
}
|
||||
|
||||
static String relative(String base, String target) {
|
||||
Path.of(base).relativize(Path.of(target)).toString()
|
||||
}
|
||||
|
||||
static File resolve(File base, Path target) {
|
||||
base.toPath().resolve(target).toFile()
|
||||
}
|
||||
|
||||
private PathUtil() {}
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package com.jessebrault.ssg.util
|
||||
|
||||
final class URLUtil {
|
||||
|
||||
static URL ofJarFile(File jarFile) {
|
||||
URI.create( "jar:file:$jarFile!/").toURL()
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package com.jessebrault.ssg.view
|
||||
|
||||
import groowt.view.StandardGStringTemplateView
|
||||
|
||||
abstract class GStringPageView extends StandardGStringTemplateView implements PageView {
|
||||
|
||||
String pageTitle
|
||||
String url
|
||||
|
||||
GStringPageView(Map<String, Object> args) {
|
||||
super(args)
|
||||
}
|
||||
|
||||
}
|
11
api/src/main/groovy/com/jessebrault/ssg/view/PageView.groovy
Normal file
11
api/src/main/groovy/com/jessebrault/ssg/view/PageView.groovy
Normal file
@ -0,0 +1,11 @@
|
||||
package com.jessebrault.ssg.view
|
||||
|
||||
import groowt.view.View
|
||||
|
||||
interface PageView extends View {
|
||||
String getPageTitle()
|
||||
void setPageTitle(String pageTitle)
|
||||
|
||||
String getUrl()
|
||||
void setUrl(String url)
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package com.jessebrault.ssg.view
|
||||
|
||||
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 SkipTemplate {}
|
@ -0,0 +1,14 @@
|
||||
package com.jessebrault.ssg.view
|
||||
|
||||
import org.jsoup.Jsoup
|
||||
|
||||
trait WithHtmlHelpers {
|
||||
|
||||
String prettyFormat(String html) {
|
||||
Jsoup.parse(html).with {
|
||||
outputSettings().prettyPrint(true)
|
||||
it.toString()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package com.jessebrault.ssg.view
|
||||
|
||||
import com.jessebrault.ssg.util.Diagnostic
|
||||
import groovy.transform.TupleConstructor
|
||||
import com.jessebrault.fp.either.Either
|
||||
import groowt.view.component.ComponentTemplate
|
||||
import groowt.view.component.ViewComponent
|
||||
import groowt.view.component.compiler.ComponentTemplateClassFactory
|
||||
import groowt.view.component.compiler.source.ComponentTemplateSource
|
||||
import groowt.view.component.web.compiler.DefaultWebViewComponentTemplateCompileUnit
|
||||
|
||||
@TupleConstructor
|
||||
class WvcCompiler {
|
||||
|
||||
private static class SsgWvcTemplateCompileUnit extends DefaultWebViewComponentTemplateCompileUnit {
|
||||
|
||||
SsgWvcTemplateCompileUnit(
|
||||
String descriptiveName,
|
||||
Class<? extends ViewComponent> forClass,
|
||||
ComponentTemplateSource source,
|
||||
String defaultPackageName,
|
||||
GroovyClassLoader groovyClassLoader
|
||||
) {
|
||||
super(descriptiveName, forClass, source, defaultPackageName)
|
||||
this.groovyCompilationUnit.setClassLoader(groovyClassLoader)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
final GroovyClassLoader groovyClassLoader
|
||||
final ComponentTemplateClassFactory templateClassFactory
|
||||
|
||||
Either<Diagnostic, ComponentTemplate> compileTemplate(
|
||||
Class<? extends ViewComponent> componentClass,
|
||||
String resourceName
|
||||
) {
|
||||
def templateUrl = componentClass.getResource(resourceName)
|
||||
if (templateUrl == null) {
|
||||
return Either.left(new Diagnostic(
|
||||
"Could not find templateResource: $resourceName"
|
||||
))
|
||||
}
|
||||
def source = ComponentTemplateSource.of(templateUrl)
|
||||
def compileUnit = new SsgWvcTemplateCompileUnit(
|
||||
source.descriptiveName,
|
||||
componentClass,
|
||||
source,
|
||||
componentClass.packageName,
|
||||
this.groovyClassLoader
|
||||
)
|
||||
def compileResult = compileUnit.compile()
|
||||
def templateClass = templateClassFactory.getTemplateClass(compileResult)
|
||||
def componentTemplate = templateClass.getConstructor().newInstance()
|
||||
return Either.right(componentTemplate)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.jessebrault.ssg.view
|
||||
|
||||
import groowt.view.component.AbstractViewComponent
|
||||
import groowt.view.component.ComponentTemplate
|
||||
import groowt.view.component.compiler.ComponentTemplateCompileUnit
|
||||
import groowt.view.component.compiler.source.ComponentTemplateSource
|
||||
import groowt.view.component.web.BaseWebViewComponent
|
||||
|
||||
import java.util.function.Function
|
||||
|
||||
abstract class WvcPageView extends BaseWebViewComponent implements PageView, WithHtmlHelpers {
|
||||
|
||||
String pageTitle
|
||||
String url
|
||||
|
||||
WvcPageView() {}
|
||||
|
||||
WvcPageView(ComponentTemplate template) {
|
||||
super(template)
|
||||
}
|
||||
|
||||
WvcPageView(Class<? extends ComponentTemplate> templateClass) {
|
||||
super(templateClass)
|
||||
}
|
||||
|
||||
WvcPageView(
|
||||
Function<? super Class<? extends AbstractViewComponent>, ComponentTemplateCompileUnit> compileUnitFunction
|
||||
) {
|
||||
super(compileUnitFunction)
|
||||
}
|
||||
|
||||
WvcPageView(ComponentTemplateSource source) {
|
||||
super(source)
|
||||
}
|
||||
|
||||
WvcPageView(Object source) {
|
||||
super(source)
|
||||
}
|
||||
|
||||
@Override
|
||||
void renderTo(Writer out) throws IOException {
|
||||
def sw = new StringWriter()
|
||||
super.renderTo(sw)
|
||||
out.write(this.prettyFormat(sw.toString()))
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package com.jessebrault.ssg.url
|
||||
package com.jessebrault.ssg.dsl.urlbuilder
|
||||
|
||||
class PathBasedUrlBuilderTests extends AbstractUrlBuilderTests {
|
||||
final class PathBasedUrlBuilderTests extends AbstractUrlBuilderTests {
|
||||
|
||||
@Override
|
||||
protected UrlBuilder getUrlBuilder(String targetPath, String baseUrl) {
|
@ -0,0 +1,58 @@
|
||||
package com.jessebrault.ssg.util
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals
|
||||
import static org.junit.jupiter.api.Assertions.assertNull
|
||||
|
||||
final class ExtensionUtilTests {
|
||||
|
||||
static class StripExtensionTests {
|
||||
|
||||
@Test
|
||||
void simple() {
|
||||
assertEquals('test', ExtensionUtil.stripExtension('test.txt'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void withSlashes() {
|
||||
assertEquals('test/test', ExtensionUtil.stripExtension('test/test.txt'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void withMultipleExtensions() {
|
||||
assertEquals('test.txt', ExtensionUtil.stripExtension('test.txt.html'))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class GetExtensionTests {
|
||||
|
||||
@Test
|
||||
void simple() {
|
||||
assertEquals('.txt', ExtensionUtil.getExtension('test.txt'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void withSlashes() {
|
||||
assertEquals('.txt', ExtensionUtil.getExtension('test/test.txt'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void withMultipleExtensions() {
|
||||
assertEquals('.txt', ExtensionUtil.getExtension('test.test.txt'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void noExtensionReturnsNull() {
|
||||
assertNull(ExtensionUtil.getExtension('NO_EXTENSION'))
|
||||
}
|
||||
|
||||
@Test
|
||||
void dotFileReturnsNull() {
|
||||
assertNull(ExtensionUtil.getExtension('.dot_file'))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.jessebrault.ssg.url
|
||||
package com.jessebrault.ssg.dsl.urlbuilder
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@ -6,7 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals
|
||||
|
||||
abstract class AbstractUrlBuilderTests {
|
||||
|
||||
protected abstract UrlBuilder getUrlBuilder(String targetPath, String baseUrl);
|
||||
protected abstract UrlBuilder getUrlBuilder(String targetPath, String baseUrl)
|
||||
|
||||
@Test
|
||||
void upDownDown() {
|
@ -0,0 +1,51 @@
|
||||
package com.jessebrault.ssg.util
|
||||
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
import java.nio.file.Path
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals
|
||||
|
||||
final class FileAssertions {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(FileAssertions)
|
||||
|
||||
private static Map<String, Object> fileToMap(File file) {
|
||||
[
|
||||
type: file.isDirectory() ? 'directory' : (file.isFile() ? 'file' : null),
|
||||
text: file.file ? file.text : null
|
||||
]
|
||||
}
|
||||
|
||||
static void assertFileStructureAndContents(
|
||||
File expectedBaseDir,
|
||||
File actualBaseDir
|
||||
) {
|
||||
final Map<Path, File> expectedPathsAndFiles = [:]
|
||||
def expectedBasePath = expectedBaseDir.toPath()
|
||||
expectedBaseDir.eachFileRecurse {
|
||||
def relativePath = expectedBasePath.relativize(it.toPath())
|
||||
expectedPathsAndFiles[relativePath] = it
|
||||
}
|
||||
logger.debug('expectedPaths: {}', expectedPathsAndFiles.keySet())
|
||||
|
||||
expectedPathsAndFiles.forEach { relativePath, expectedFile ->
|
||||
def actualFile = new File(actualBaseDir, relativePath.toString())
|
||||
logger.debug(
|
||||
'relativePath: {}, expectedFile: {}, actualFile: {}',
|
||||
relativePath,
|
||||
fileToMap(expectedFile),
|
||||
fileToMap(actualFile)
|
||||
)
|
||||
assertEquals(expectedFile.directory, actualFile.directory)
|
||||
assertEquals(expectedFile.file, actualFile.file)
|
||||
if (expectedFile.file) {
|
||||
assertEquals(expectedFile.text, actualFile.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private FileAssertions() {}
|
||||
|
||||
}
|
@ -8,4 +8,10 @@ repositories {
|
||||
|
||||
asciidoctor {
|
||||
sourceDir = 'docs/asciidoc'
|
||||
}
|
||||
|
||||
asciidoctorj {
|
||||
modules {
|
||||
diagram.use()
|
||||
}
|
||||
}
|
@ -1,3 +1,27 @@
|
||||
plugins {
|
||||
id 'groovy-gradle-plugin'
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://archiva.jessebrault.com/repository/internal/'
|
||||
|
||||
credentials {
|
||||
username System.getenv('JBARCHIVA_USERNAME')
|
||||
password System.getenv('JBARCHIVA_PASSWORD')
|
||||
}
|
||||
}
|
||||
|
||||
maven {
|
||||
url 'https://archiva.jessebrault.com/repository/snapshots/'
|
||||
|
||||
credentials {
|
||||
username System.getenv('JBARCHIVA_USERNAME')
|
||||
password System.getenv('JBARCHIVA_PASSWORD')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.jessebrault.jbarchiva:jbarchiva:0.2.2'
|
||||
}
|
||||
|
67
buildSrc/src/main/groovy/ssg-common.gradle
Normal file
67
buildSrc/src/main/groovy/ssg-common.gradle
Normal file
@ -0,0 +1,67 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
group 'com.jessebrault.ssg'
|
||||
version '0.6.0-SNAPSHOT'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
maven {
|
||||
name = 'Gitea'
|
||||
url = uri('https://git.jessebrault.com/api/packages/jessebrault/maven')
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
testing {
|
||||
canBeConsumed = false
|
||||
canBeResolved = false
|
||||
}
|
||||
testImplementation {
|
||||
extendsFrom configurations.testing
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation libs.slf4j.api
|
||||
|
||||
testing libs.slf4j.api
|
||||
testing libs.junit.jupiter.api
|
||||
testing libs.mockito.core
|
||||
testing libs.mockito.junit.jupiter
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(21)
|
||||
}
|
||||
}
|
||||
|
||||
testing {
|
||||
suites {
|
||||
test {
|
||||
useJUnitJupiter()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
name = 'Gitea'
|
||||
url = uri('https://git.jessebrault.com/api/packages/jessebrault/maven')
|
||||
|
||||
credentials(HttpHeaderCredentials) {
|
||||
name = "Authorization"
|
||||
value = "token ${System.getenv("GITEA_ACCESS_TOKEN")}"
|
||||
}
|
||||
|
||||
authentication {
|
||||
header(HttpHeaderAuthentication)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
plugins {
|
||||
id 'groovy'
|
||||
id 'java-library'
|
||||
id 'java-test-fixtures'
|
||||
}
|
||||
|
||||
group 'com.jessebrault.ssg'
|
||||
version '0.1.0'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/org.apache.groovy/groovy
|
||||
api 'org.apache.groovy:groovy:4.0.9'
|
||||
|
||||
// https://mvnrepository.com/artifact/org.jetbrains/annotations
|
||||
api 'org.jetbrains:annotations:24.0.0'
|
||||
|
||||
/**
|
||||
* Logging
|
||||
*/
|
||||
// https://mvnrepository.com/artifact/org.slf4j/slf4j-api
|
||||
implementation 'org.slf4j:slf4j-api:1.7.36'
|
||||
|
||||
testFixturesImplementation 'org.slf4j:slf4j-api:1.7.36'
|
||||
|
||||
/**
|
||||
* TESTING
|
||||
*/
|
||||
// https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api
|
||||
testFixturesApi 'org.junit.jupiter:junit-jupiter-api:5.9.2'
|
||||
|
||||
// https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine
|
||||
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2'
|
||||
|
||||
/**
|
||||
* Mockito
|
||||
*/
|
||||
// https://mvnrepository.com/artifact/org.mockito/mockito-core
|
||||
testFixturesApi 'org.mockito:mockito-core:5.1.1'
|
||||
|
||||
// https://mvnrepository.com/artifact/org.mockito/mockito-junit-jupiter
|
||||
testFixturesApi 'org.mockito:mockito-junit-jupiter:5.1.1'
|
||||
|
||||
/**
|
||||
* Test Logging
|
||||
*/
|
||||
// https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl
|
||||
testRuntimeOnly 'org.apache.logging.log4j:log4j-slf4j-impl:2.19.0'
|
||||
|
||||
// https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core
|
||||
testRuntimeOnly 'org.apache.logging.log4j:log4j-core:2.19.0'
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
@ -1,29 +1,24 @@
|
||||
plugins {
|
||||
id 'ssg.common'
|
||||
id 'ssg-common'
|
||||
id 'groovy'
|
||||
id 'application'
|
||||
id 'maven-publish'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url 'https://repo.gradle.org/gradle/libs-releases' }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':lib')
|
||||
implementation project(':api')
|
||||
implementation project(':ssg-gradle-model')
|
||||
implementation libs.picocli
|
||||
implementation libs.log4j2.api
|
||||
implementation libs.log4j2.core
|
||||
implementation "org.gradle:gradle-tooling-api:8.14.1"
|
||||
|
||||
// https://mvnrepository.com/artifact/info.picocli/picocli
|
||||
implementation 'info.picocli:picocli:4.7.1'
|
||||
|
||||
/**
|
||||
* Logging
|
||||
*/
|
||||
// https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api
|
||||
implementation 'org.apache.logging.log4j:log4j-api:2.19.0'
|
||||
|
||||
// https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl
|
||||
runtimeOnly 'org.apache.logging.log4j:log4j-slf4j-impl:2.19.0'
|
||||
|
||||
// https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core
|
||||
implementation 'org.apache.logging.log4j:log4j-core:2.19.0'
|
||||
runtimeOnly libs.log4j2.slf4j2.impl
|
||||
}
|
||||
|
||||
application {
|
||||
@ -31,8 +26,16 @@ application {
|
||||
applicationName = 'ssg'
|
||||
}
|
||||
|
||||
java {
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
jar {
|
||||
archivesBaseName = "ssg-cli"
|
||||
archivesBaseName = 'ssg-cli'
|
||||
}
|
||||
|
||||
sourcesJar {
|
||||
archiveBaseName = 'ssg-cli'
|
||||
}
|
||||
|
||||
distributions {
|
||||
@ -40,4 +43,13 @@ distributions {
|
||||
//noinspection GroovyAssignabilityCheck
|
||||
distributionBaseName = 'ssg'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
create('ssgCli', MavenPublication) {
|
||||
artifactId = 'cli'
|
||||
from components.java
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,113 +1,146 @@
|
||||
package com.jessebrault.ssg
|
||||
|
||||
import com.jessebrault.ssg.buildscript.GroovyBuildScriptRunner
|
||||
import com.jessebrault.ssg.task.Output
|
||||
import com.jessebrault.ssg.part.GspPartRenderer
|
||||
import com.jessebrault.ssg.part.PartFilePartsProvider
|
||||
import com.jessebrault.ssg.part.PartType
|
||||
import com.jessebrault.ssg.specialpage.GspSpecialPageRenderer
|
||||
import com.jessebrault.ssg.specialpage.SpecialPageFileSpecialPagesProvider
|
||||
import com.jessebrault.ssg.specialpage.SpecialPageType
|
||||
import com.jessebrault.ssg.task.TaskExecutorContext
|
||||
import com.jessebrault.ssg.template.GspTemplateRenderer
|
||||
import com.jessebrault.ssg.template.TemplateFileTemplatesProvider
|
||||
import com.jessebrault.ssg.template.TemplateType
|
||||
import com.jessebrault.ssg.text.MarkdownExcerptGetter
|
||||
import com.jessebrault.ssg.text.MarkdownFrontMatterGetter
|
||||
import com.jessebrault.ssg.text.MarkdownTextRenderer
|
||||
import com.jessebrault.ssg.text.TextFileTextsProvider
|
||||
import com.jessebrault.ssg.text.TextType
|
||||
import com.jessebrault.ssg.gradle.SsgBuildModel
|
||||
import com.jessebrault.ssg.util.Diagnostic
|
||||
import com.jessebrault.ssg.util.URLUtil
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.apache.logging.log4j.Logger
|
||||
import org.gradle.tooling.GradleConnector
|
||||
import picocli.CommandLine
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
|
||||
abstract class AbstractBuildCommand extends AbstractSubCommand {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(AbstractBuildCommand)
|
||||
|
||||
protected final Collection<Build> builds = []
|
||||
protected final StaticSiteGenerator ssg
|
||||
@CommandLine.Option(
|
||||
names = ['-b', '--build', '--build-name'],
|
||||
description = 'The name of a build to execute. May be the name of a script (without the .groovy extension) in a build script dir, or a fully-qualified-name of a build script on the classpath or nested in a build script dir.',
|
||||
arity = '1..*',
|
||||
required = true,
|
||||
paramLabel = 'buildName',
|
||||
defaultValue = 'default'
|
||||
)
|
||||
Set<String> requestedBuilds
|
||||
|
||||
AbstractBuildCommand() {
|
||||
// Configure
|
||||
def markdownText = new TextType(['.md'], new MarkdownTextRenderer(), new MarkdownFrontMatterGetter(), new MarkdownExcerptGetter())
|
||||
def gspTemplate = new TemplateType(['.gsp'], new GspTemplateRenderer())
|
||||
def gspPart = new PartType(['.gsp'], new GspPartRenderer())
|
||||
def gspSpecialPage = new SpecialPageType(['.gsp'], new GspSpecialPageRenderer())
|
||||
@CommandLine.Option(
|
||||
names = ['--build-script-dir'],
|
||||
description = 'A directory containing build scripts, relative to the project directory.',
|
||||
arity = '1..*',
|
||||
required = true,
|
||||
defaultValue = 'ssg',
|
||||
paramLabel = 'build-script-dir'
|
||||
)
|
||||
Set<Path> buildScriptDirs
|
||||
|
||||
def defaultTextsProvider = new TextFileTextsProvider(new File('texts'), [markdownText])
|
||||
def defaultTemplatesProvider = new TemplateFileTemplatesProvider(new File('templates'), [gspTemplate])
|
||||
def defaultPartsProvider = new PartFilePartsProvider(new File('parts'), [gspPart])
|
||||
def defaultSpecialPagesProvider = new SpecialPageFileSpecialPagesProvider(new File('specialPages'), [gspSpecialPage])
|
||||
@CommandLine.Option(
|
||||
names = ['-A', '--script-arg'],
|
||||
description = 'Named args to pass directly to the build script.'
|
||||
)
|
||||
Map<String, String> scriptArgs
|
||||
|
||||
def defaultConfig = new Config(
|
||||
textProviders: [defaultTextsProvider],
|
||||
templatesProviders: [defaultTemplatesProvider],
|
||||
partsProviders: [defaultPartsProvider],
|
||||
specialPagesProviders: [defaultSpecialPagesProvider]
|
||||
)
|
||||
def defaultSiteSpec = new SiteSpec(
|
||||
name: '',
|
||||
baseUrl: ''
|
||||
)
|
||||
def defaultGlobals = [:]
|
||||
@CommandLine.Option(
|
||||
names = ['-g', '--gradle'],
|
||||
description = 'If set, build the associated Gradle project and use its output in the ssg build process.',
|
||||
negatable = true
|
||||
)
|
||||
boolean gradle
|
||||
|
||||
// Run build script, if applicable
|
||||
if (new File('ssgBuilds.groovy').exists()) {
|
||||
logger.info('found buildScript: ssgBuilds.groovy')
|
||||
def buildScriptRunner = new GroovyBuildScriptRunner()
|
||||
this.builds.addAll(buildScriptRunner.runBuildScript('ssgBuilds.groovy', defaultConfig, defaultGlobals))
|
||||
logger.debug('after running ssgBuilds.groovy, builds: {}', this.builds)
|
||||
}
|
||||
@CommandLine.Option(
|
||||
names = ['--gradle-project-dir'],
|
||||
description = 'The Gradle project directory for the project containing Pages, Components, Models, etc., relative to the project directory.',
|
||||
defaultValue = '.'
|
||||
)
|
||||
Path gradleProjectDir
|
||||
|
||||
if (this.builds.empty) {
|
||||
// Add default build
|
||||
builds << new Build(
|
||||
'default',
|
||||
defaultConfig,
|
||||
defaultSiteSpec,
|
||||
defaultGlobals,
|
||||
new File('build')
|
||||
)
|
||||
}
|
||||
@CommandLine.Option(
|
||||
names = ['-l', '--lib-dir'],
|
||||
description = 'A lib dir where jars containing Pages, Components, Models, etc., can be found, relative to the project directory.',
|
||||
defaultValue = 'lib'
|
||||
)
|
||||
Set<Path> libDirs
|
||||
|
||||
// Get ssg object
|
||||
this.ssg = new SimpleStaticSiteGenerator()
|
||||
}
|
||||
@CommandLine.Option(
|
||||
names = ['--dry-run'],
|
||||
description = 'Do a dry run of the build; do not actually output anything.'
|
||||
)
|
||||
boolean dryRun
|
||||
|
||||
protected final Integer doBuild() {
|
||||
logger.traceEntry('builds: {}, ssg: {}', this.builds, this.ssg)
|
||||
protected StaticSiteGenerator staticSiteGenerator = null
|
||||
|
||||
def hadDiagnostics = false
|
||||
// Do each build
|
||||
this.builds.each {
|
||||
def result = this.ssg.generate(it)
|
||||
if (result.hasDiagnostics()) {
|
||||
hadDiagnostics = true
|
||||
result.diagnostics.each {
|
||||
logger.error(it.message)
|
||||
protected final Integer doSingleBuild(String buildName) {
|
||||
logger.traceEntry('buildName: {}', buildName)
|
||||
|
||||
if (this.staticSiteGenerator == null) {
|
||||
def groovyClassLoader = new GroovyClassLoader(this.class.classLoader)
|
||||
|
||||
if (this.gradle) {
|
||||
def projectConnection = GradleConnector.newConnector()
|
||||
.forProjectDirectory(
|
||||
this.commonCliOptions.projectDir.toPath().resolve(this.gradleProjectDir).toFile()
|
||||
).connect()
|
||||
def ssgGradleModel = projectConnection.getModel(SsgBuildModel)
|
||||
|
||||
ssgGradleModel.buildOutputLibs.each { outputLib ->
|
||||
if (outputLib.name.endsWith('.jar')) {
|
||||
groovyClassLoader.addURL(URLUtil.ofJarFile(outputLib))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
def tasks = result.get()
|
||||
Collection<Diagnostic> executionDiagnostics = []
|
||||
def context = new TaskExecutorContext(
|
||||
it,
|
||||
tasks,
|
||||
this.ssg.taskTypes,
|
||||
{ Collection<Diagnostic> diagnostics ->
|
||||
executionDiagnostics.addAll(diagnostics)
|
||||
|
||||
ssgGradleModel.runtimeClasspath.each { classpathElement ->
|
||||
if (classpathElement.name.endsWith('.jar')) {
|
||||
groovyClassLoader.addURL(URLUtil.ofJarFile(classpathElement))
|
||||
}
|
||||
}
|
||||
|
||||
projectConnection.newBuild().forTasks('ssgJars').run()
|
||||
projectConnection.close()
|
||||
}
|
||||
|
||||
this.libDirs.each { libDir ->
|
||||
def resolved = this.commonCliOptions.projectDir.toPath().resolve(libDir)
|
||||
if (Files.exists(resolved)) {
|
||||
Files.walk(resolved).each {
|
||||
def asFile = it.toFile()
|
||||
if (asFile.isFile() && asFile.name.endsWith('.jar')) {
|
||||
groovyClassLoader.addURL(URLUtil.ofJarFile(asFile))
|
||||
}
|
||||
)
|
||||
result.get().each { it.execute(context) }
|
||||
if (!executionDiagnostics.isEmpty()) {
|
||||
hadDiagnostics = true
|
||||
executionDiagnostics.each {
|
||||
logger.error(it.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def buildScriptDirUrls = this.buildScriptDirs.collect {
|
||||
def withProjectDir = new File(this.commonCliOptions.projectDir, it.toString())
|
||||
withProjectDir.toURI().toURL()
|
||||
} as URL[]
|
||||
|
||||
this.staticSiteGenerator = new DefaultStaticSiteGenerator(
|
||||
groovyClassLoader,
|
||||
buildScriptDirUrls,
|
||||
this.dryRun
|
||||
)
|
||||
}
|
||||
|
||||
logger.traceExit(hadDiagnostics ? 1 : 0)
|
||||
final Collection<Diagnostic> diagnostics = this.staticSiteGenerator.doBuild(
|
||||
this.commonCliOptions.projectDir,
|
||||
buildName,
|
||||
buildName,
|
||||
this.scriptArgs ?: [:]
|
||||
)
|
||||
|
||||
if (!diagnostics.isEmpty()) {
|
||||
diagnostics.each {
|
||||
logger.error(it.message)
|
||||
if (it.exception != null) {
|
||||
it.exception.printStackTrace()
|
||||
}
|
||||
}
|
||||
logger.traceExit(1)
|
||||
} else {
|
||||
logger.traceExit(0)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -12,10 +12,10 @@ abstract class AbstractSubCommand implements Callable<Integer> {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(AbstractSubCommand)
|
||||
|
||||
@CommandLine.ParentCommand
|
||||
StaticSiteGeneratorCli cli
|
||||
@CommandLine.Mixin
|
||||
CommonCliOptions commonCliOptions
|
||||
|
||||
abstract Integer doSubCommand()
|
||||
protected abstract Integer doSubCommand()
|
||||
|
||||
@Override
|
||||
Integer call() {
|
||||
@ -26,14 +26,15 @@ abstract class AbstractSubCommand implements Callable<Integer> {
|
||||
def configuration = context.getConfiguration()
|
||||
def rootLoggerConfig = configuration.getRootLogger()
|
||||
|
||||
if (this.cli.logLevel?.info) {
|
||||
def logLevel = this.commonCliOptions.logLevel
|
||||
if (logLevel == LogLevel.INFO) {
|
||||
rootLoggerConfig.level = Level.INFO
|
||||
} else if (this.cli.logLevel?.debug) {
|
||||
} else if (logLevel == LogLevel.DEBUG) {
|
||||
rootLoggerConfig.level = Level.DEBUG
|
||||
} else if (this.cli.logLevel?.trace) {
|
||||
} else if (logLevel == LogLevel.TRACE) {
|
||||
rootLoggerConfig.level = Level.TRACE
|
||||
} else {
|
||||
rootLoggerConfig.level = Level.WARN
|
||||
rootLoggerConfig.level = Level.INFO
|
||||
}
|
||||
|
||||
context.updateLoggers()
|
||||
|
@ -0,0 +1,13 @@
|
||||
package com.jessebrault.ssg
|
||||
|
||||
import picocli.CommandLine
|
||||
|
||||
class CommonCliOptions {
|
||||
|
||||
@CommandLine.Option(names = '--project-dir', defaultValue = '.', description = 'The ssg project directory.')
|
||||
File projectDir
|
||||
|
||||
@CommandLine.Option(names = '--log-level', defaultValue = 'info', description = 'The logging level.')
|
||||
LogLevel logLevel
|
||||
|
||||
}
|
5
cli/src/main/groovy/com/jessebrault/ssg/LogLevel.java
Normal file
5
cli/src/main/groovy/com/jessebrault/ssg/LogLevel.java
Normal file
@ -0,0 +1,5 @@
|
||||
package com.jessebrault.ssg;
|
||||
|
||||
public enum LogLevel {
|
||||
INFO, DEBUG, TRACE
|
||||
}
|
@ -9,13 +9,20 @@ import picocli.CommandLine
|
||||
mixinStandardHelpOptions = true,
|
||||
description = 'Builds the project.'
|
||||
)
|
||||
class SsgBuild extends AbstractBuildCommand {
|
||||
final class SsgBuild extends AbstractBuildCommand {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(SsgBuild)
|
||||
|
||||
@Override
|
||||
Integer doSubCommand() {
|
||||
this.doBuild()
|
||||
protected Integer doSubCommand() {
|
||||
logger.traceEntry()
|
||||
def result = 0
|
||||
this.requestedBuilds.each {
|
||||
if (this.doSingleBuild(it) != 0) {
|
||||
result = 1
|
||||
}
|
||||
}
|
||||
logger.traceExit(result)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,46 +9,72 @@ import picocli.CommandLine
|
||||
mixinStandardHelpOptions = true,
|
||||
description = 'Generates a blank project, optionally with some basic files.'
|
||||
)
|
||||
class SsgInit extends AbstractSubCommand {
|
||||
final class SsgInit extends AbstractSubCommand {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(SsgInit)
|
||||
static void copyResourceToFile(String resourceName, File target) {
|
||||
SsgInit.getResource(resourceName).withReader { reader ->
|
||||
target.withWriter { writer ->
|
||||
reader.transferTo(writer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@CommandLine.Option(names = ['-s', '--skeleton'], description = 'Include some basic files in the generated project.')
|
||||
boolean withSkeletonFiles
|
||||
|
||||
@Override
|
||||
Integer doSubCommand() {
|
||||
logger.traceEntry()
|
||||
new FileTreeBuilder().with {
|
||||
// Generate dirs
|
||||
static void init(File targetDir, boolean meaty) {
|
||||
new FileTreeBuilder(targetDir).with {
|
||||
dir('texts') {
|
||||
if (this.withSkeletonFiles) {
|
||||
file('hello.md', this.getClass().getResource('/hello.md').text)
|
||||
if (meaty) {
|
||||
file('hello.md').tap {
|
||||
copyResourceToFile('hello.md', it)
|
||||
}
|
||||
}
|
||||
}
|
||||
dir('pages') {
|
||||
if (meaty) {
|
||||
file('page.gsp').tap {
|
||||
copyResourceToFile('page.gsp', it)
|
||||
}
|
||||
}
|
||||
}
|
||||
dir('templates') {
|
||||
if (this.withSkeletonFiles) {
|
||||
file('hello.gsp', this.getClass().getResource('/hello.gsp').text)
|
||||
if (meaty) {
|
||||
file('hello.gsp').tap {
|
||||
copyResourceToFile('hello.gsp', it)
|
||||
}
|
||||
}
|
||||
}
|
||||
dir('parts') {
|
||||
if (this.withSkeletonFiles) {
|
||||
file('head.gsp', this.getClass().getResource('/head.gsp').text)
|
||||
}
|
||||
}
|
||||
dir('specialPages') {
|
||||
if (this.withSkeletonFiles) {
|
||||
file('specialPage.gsp', this.getClass().getResource('/specialPage.gsp').text)
|
||||
if (meaty) {
|
||||
file('head.gsp').tap {
|
||||
copyResourceToFile('head.gsp', it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate ssgBuilds.groovy
|
||||
if (this.withSkeletonFiles) {
|
||||
file('ssgBuilds.groovy', this.getClass().getResource('/ssgBuilds.groovy').text)
|
||||
if (meaty) {
|
||||
file('ssgBuilds.groovy').tap {
|
||||
copyResourceToFile('ssgBuildsMeaty.groovy', it)
|
||||
}
|
||||
} else {
|
||||
file('ssgBuilds.groovy', this.getClass().getResource('/ssgBuildsBasic.groovy').text)
|
||||
file('ssgBuilds.groovy').tap {
|
||||
copyResourceToFile('ssgBuildsBasic.groovy', it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(SsgInit)
|
||||
|
||||
@CommandLine.Option(names = ['-m', '--meaty'], description = 'Include some basic files in the generated project.')
|
||||
boolean meaty
|
||||
|
||||
@CommandLine.Option(names = '--targetDir', description = 'The directory in which to generate the project')
|
||||
File target = new File('.')
|
||||
|
||||
@Override
|
||||
protected Integer doSubCommand() {
|
||||
logger.traceEntry()
|
||||
init(this.target, this.meaty)
|
||||
logger.traceExit(0)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,24 +1,16 @@
|
||||
package com.jessebrault.ssg
|
||||
|
||||
import com.jessebrault.ssg.provider.WithWatchableDir
|
||||
import groovy.io.FileType
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.apache.logging.log4j.Logger
|
||||
import picocli.CommandLine
|
||||
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardWatchEventKinds
|
||||
import java.nio.file.WatchEvent
|
||||
import java.nio.file.WatchKey
|
||||
|
||||
@CommandLine.Command(
|
||||
hidden = true,
|
||||
name = 'watch',
|
||||
mixinStandardHelpOptions = true,
|
||||
description = 'Run in watch mode, rebuilding the project whenever files are created/updated/deleted.'
|
||||
)
|
||||
class SsgWatch extends AbstractBuildCommand {
|
||||
final class SsgWatch extends AbstractBuildCommand {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(SsgWatch)
|
||||
|
||||
@ -26,88 +18,88 @@ class SsgWatch extends AbstractBuildCommand {
|
||||
Integer doSubCommand() {
|
||||
logger.traceEntry()
|
||||
|
||||
// Setup watchService and watchKeys
|
||||
def watchService = FileSystems.getDefault().newWatchService()
|
||||
Map<WatchKey, Path> watchKeys = [:]
|
||||
|
||||
// Our Closure to register a directory path
|
||||
def registerPath = { Path path ->
|
||||
if (!Files.isDirectory(path)) {
|
||||
throw new IllegalArgumentException('path must be a directory, given: ' + path)
|
||||
}
|
||||
logger.debug('registering dir with path: {}', path)
|
||||
def watchKey = path.register(
|
||||
watchService,
|
||||
StandardWatchEventKinds.ENTRY_CREATE,
|
||||
StandardWatchEventKinds.ENTRY_DELETE,
|
||||
StandardWatchEventKinds.ENTRY_MODIFY
|
||||
)
|
||||
watchKeys[watchKey] = path
|
||||
logger.debug('watchKeys: {}', watchKeys)
|
||||
}
|
||||
|
||||
// Get all base watchableDirs
|
||||
Collection<WithWatchableDir> watchableProviders = []
|
||||
this.builds.each {
|
||||
it.config.textProviders.each {
|
||||
if (it instanceof WithWatchableDir) {
|
||||
watchableProviders << it
|
||||
}
|
||||
}
|
||||
it.config.templatesProviders.each {
|
||||
if (it instanceof WithWatchableDir) {
|
||||
watchableProviders << it
|
||||
}
|
||||
}
|
||||
it.config.partsProviders.each {
|
||||
if (it instanceof WithWatchableDir) {
|
||||
watchableProviders << it
|
||||
}
|
||||
}
|
||||
it.config.specialPagesProviders.each {
|
||||
if (it instanceof WithWatchableDir) {
|
||||
watchableProviders << it
|
||||
}
|
||||
}
|
||||
}
|
||||
// register them and their child directories using the Closure above
|
||||
watchableProviders.each {
|
||||
def baseDirFile = it.watchableDir
|
||||
registerPath(baseDirFile.toPath())
|
||||
baseDirFile.eachFile(FileType.DIRECTORIES) {
|
||||
registerPath(it.toPath())
|
||||
}
|
||||
}
|
||||
|
||||
//noinspection GroovyInfiniteLoopStatement
|
||||
while (true) {
|
||||
def watchKey = watchService.take()
|
||||
def path = watchKeys[watchKey]
|
||||
if (path == null) {
|
||||
logger.warn('unexpected watchKey: {}', watchKey)
|
||||
} else {
|
||||
watchKey.pollEvents().each {
|
||||
assert it instanceof WatchEvent<Path>
|
||||
def childName = it.context()
|
||||
def childPath = path.resolve(childName)
|
||||
if (it.kind() == StandardWatchEventKinds.ENTRY_CREATE && Files.isDirectory(childPath)) {
|
||||
registerPath(childPath)
|
||||
} else if (Files.isRegularFile(childPath)) {
|
||||
logger.debug('detected {} for regularFile with path {}', it.kind(), childPath)
|
||||
def t = new Thread({
|
||||
this.doBuild()
|
||||
})
|
||||
t.setName('workerThread')
|
||||
t.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
def valid = watchKey.reset()
|
||||
if (!valid) {
|
||||
def removedPath = watchKeys.remove(watchKey)
|
||||
logger.debug('removed path: {}', removedPath)
|
||||
}
|
||||
}
|
||||
// // Setup watchService and watchKeys
|
||||
// def watchService = FileSystems.getDefault().newWatchService()
|
||||
// Map<WatchKey, Path> watchKeys = [:]
|
||||
//
|
||||
// // Our Closure to register a directory path
|
||||
// def registerPath = { Path path ->
|
||||
// if (!Files.isDirectory(path)) {
|
||||
// throw new IllegalArgumentException('path must be a directory, given: ' + path)
|
||||
// }
|
||||
// logger.debug('registering dir with path: {}', path)
|
||||
// def watchKey = path.register(
|
||||
// watchService,
|
||||
// StandardWatchEventKinds.ENTRY_CREATE,
|
||||
// StandardWatchEventKinds.ENTRY_DELETE,
|
||||
// StandardWatchEventKinds.ENTRY_MODIFY
|
||||
// )
|
||||
// watchKeys[watchKey] = path
|
||||
// logger.debug('watchKeys: {}', watchKeys)
|
||||
// }
|
||||
//
|
||||
// // Get all base watchableDirs
|
||||
// Collection<WithWatchableDir> watchableProviders = []
|
||||
// this.builds.each {
|
||||
// it.config.textProviders.each {
|
||||
// if (it instanceof WithWatchableDir) {
|
||||
// watchableProviders << it
|
||||
// }
|
||||
// }
|
||||
// it.config.templatesProviders.each {
|
||||
// if (it instanceof WithWatchableDir) {
|
||||
// watchableProviders << it
|
||||
// }
|
||||
// }
|
||||
// it.config.partsProviders.each {
|
||||
// if (it instanceof WithWatchableDir) {
|
||||
// watchableProviders << it
|
||||
// }
|
||||
// }
|
||||
// it.config.specialPagesProviders.each {
|
||||
// if (it instanceof WithWatchableDir) {
|
||||
// watchableProviders << it
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// // register them and their child directories using the Closure above
|
||||
// watchableProviders.each {
|
||||
// def baseDirFile = it.watchableDir
|
||||
// registerPath(baseDirFile.toPath())
|
||||
// baseDirFile.eachFile(FileType.DIRECTORIES) {
|
||||
// registerPath(it.toPath())
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// //noinspection GroovyInfiniteLoopStatement
|
||||
// while (true) {
|
||||
// def watchKey = watchService.take()
|
||||
// def path = watchKeys[watchKey]
|
||||
// if (path == null) {
|
||||
// logger.warn('unexpected watchKey: {}', watchKey)
|
||||
// } else {
|
||||
// watchKey.pollEvents().each {
|
||||
// assert it instanceof WatchEvent<Path>
|
||||
// def childName = it.context()
|
||||
// def childPath = path.resolve(childName)
|
||||
// if (it.kind() == StandardWatchEventKinds.ENTRY_CREATE && Files.isDirectory(childPath)) {
|
||||
// registerPath(childPath)
|
||||
// } else if (Files.isRegularFile(childPath)) {
|
||||
// logger.debug('detected {} for regularFile with path {}', it.kind(), childPath)
|
||||
// def t = new Thread({
|
||||
// this.doBuild()
|
||||
// })
|
||||
// t.setName('workerThread')
|
||||
// t.start()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// def valid = watchKey.reset()
|
||||
// if (!valid) {
|
||||
// def removedPath = watchKeys.remove(watchKey)
|
||||
// logger.debug('removed path: {}', removedPath)
|
||||
// }
|
||||
// }
|
||||
|
||||
//noinspection GroovyUnreachableStatement
|
||||
logger.traceExit(0)
|
||||
|
@ -5,30 +5,17 @@ import picocli.CommandLine
|
||||
@CommandLine.Command(
|
||||
name = 'ssg',
|
||||
mixinStandardHelpOptions = true,
|
||||
version = '0.0.1-SNAPSHOT',
|
||||
description = 'Generates a set of html files from a given configuration.',
|
||||
version = '0.4.0',
|
||||
description = 'A static site generator which can interface with Gradle for high extensibility.',
|
||||
subcommands = [SsgInit, SsgBuild, SsgWatch]
|
||||
)
|
||||
class StaticSiteGeneratorCli {
|
||||
final class StaticSiteGeneratorCli {
|
||||
|
||||
static void main(String[] args) {
|
||||
System.exit(new CommandLine(StaticSiteGeneratorCli).execute(args))
|
||||
System.exit(new CommandLine(StaticSiteGeneratorCli).with {
|
||||
caseInsensitiveEnumValuesAllowed = true
|
||||
execute(args)
|
||||
})
|
||||
}
|
||||
|
||||
static class LogLevel {
|
||||
|
||||
@CommandLine.Option(names = ['--info'], description = 'Log at INFO level.')
|
||||
boolean info
|
||||
|
||||
@CommandLine.Option(names = ['--debug'], description = 'Log at DEBUG level.')
|
||||
boolean debug
|
||||
|
||||
@CommandLine.Option(names = ['--trace'], description = 'Log at TRACE level.')
|
||||
boolean trace
|
||||
|
||||
}
|
||||
|
||||
@CommandLine.ArgGroup(exclusive = true, heading = 'Log Level')
|
||||
LogLevel logLevel
|
||||
|
||||
}
|
||||
|
10
cli/src/main/resources/com/jessebrault/ssg/default.groovy
Normal file
10
cli/src/main/resources/com/jessebrault/ssg/default.groovy
Normal file
@ -0,0 +1,10 @@
|
||||
import com.jessebrault.ssg.buildscript.BuildScriptBase
|
||||
import groovy.transform.BaseScript
|
||||
|
||||
@BaseScript
|
||||
BuildScriptBase base
|
||||
|
||||
build {
|
||||
siteName 'My Site'
|
||||
baseUrl 'https://mysite.com'
|
||||
}
|
10
cli/src/main/resources/com/jessebrault/ssg/preview.groovy
Normal file
10
cli/src/main/resources/com/jessebrault/ssg/preview.groovy
Normal file
@ -0,0 +1,10 @@
|
||||
import com.jessebrault.ssg.buildscript.BuildScriptBase
|
||||
import groovy.transform.BaseScript
|
||||
|
||||
@BaseScript
|
||||
BuildScriptBase base
|
||||
|
||||
build {
|
||||
extending 'default'
|
||||
baseUrl baseUrl.map { defaultBaseUrl -> defaultBaseUrl + '/preview' }
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
import com.jessebrault.ssg.buildscript.BuildScriptBase
|
||||
import groovy.transform.BaseScript
|
||||
|
||||
@BaseScript
|
||||
BuildScriptBase base
|
||||
|
||||
build {
|
||||
extending 'default'
|
||||
}
|
@ -1,10 +1,11 @@
|
||||
<html>
|
||||
<%
|
||||
println "delegate.text: $delegate.text"
|
||||
out << parts['head.gsp'].render([
|
||||
title: "${ globals.siteTitle }: ${ frontMatter.title }"
|
||||
title: "${ siteSpec.name }: ${ text.frontMatter.title }"
|
||||
])
|
||||
%>
|
||||
<body>
|
||||
<%= text %>
|
||||
<%= text.render() %>
|
||||
</body>
|
||||
</html>
|
@ -3,15 +3,17 @@
|
||||
<Appenders>
|
||||
<Console name="standard" target="SYSTEM_OUT">
|
||||
<PatternLayout>
|
||||
<MarkerPatternSelector defaultPattern="%highlight{%-5level} %logger{1}: %msg%n%ex">
|
||||
<PatternMatch key="FLOW" pattern="%highlight{%-5level} %logger{1}: %markerSimpleName %msg%n%ex" />
|
||||
<MarkerPatternSelector defaultPattern="%highlight{%-5level} %logger: %msg%n%ex">
|
||||
<PatternMatch key="FLOW" pattern="%highlight{%-5level} %logger: %markerSimpleName %msg%n%ex" />
|
||||
</MarkerPatternSelector>
|
||||
</PatternLayout>
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Root level="warn">
|
||||
<Root level="info">
|
||||
<AppenderRef ref="standard" />
|
||||
</Root>
|
||||
<Logger name="org.gradle" level="off" />
|
||||
<Logger name="groowt" level="off" />
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
</Configuration>
|
||||
|
8
cli/src/main/resources/page.gsp
Normal file
8
cli/src/main/resources/page.gsp
Normal file
@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>${ siteSpec.name }: Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<%= texts.find { it.path == 'hello.md' }.render() %>
|
||||
</body>
|
||||
</html>
|
@ -1,8 +0,0 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>${ globals.siteTitle }: Special Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<%= texts.find { it.path == 'hello' }.render() %>
|
||||
</body>
|
||||
</html>
|
@ -1,14 +0,0 @@
|
||||
// This file was auto-generated by the ssg init command.
|
||||
|
||||
build {
|
||||
name = 'My Simple Build'
|
||||
outDir = new File('mySimpleBuild')
|
||||
|
||||
config {
|
||||
// Config options here
|
||||
}
|
||||
|
||||
globals {
|
||||
siteTitle = 'My Simple Site'
|
||||
}
|
||||
}
|
@ -1 +1 @@
|
||||
// This file was auto-generated by the ssg init command.
|
||||
// This file was auto-generated by the ssg init command.
|
||||
|
22
cli/src/main/resources/ssgBuildsMeaty.groovy
Normal file
22
cli/src/main/resources/ssgBuildsMeaty.groovy
Normal file
@ -0,0 +1,22 @@
|
||||
// This file was auto-generated by the ssg init command.
|
||||
|
||||
import groovy.transform.BaseScript
|
||||
import com.jessebrault.ssg.buildscript.BuildScriptBase
|
||||
|
||||
@BaseScript
|
||||
BuildScriptBase b
|
||||
|
||||
abstractBuild(name: 'mySiteAll', extending: 'default') {
|
||||
siteSpec {
|
||||
name = 'My Site'
|
||||
baseUrl = 'https://mysite.com'
|
||||
}
|
||||
}
|
||||
|
||||
build(name: 'production', extending: 'mySiteAll') { }
|
||||
|
||||
build(name: 'preview', extending: 'mySiteAll') {
|
||||
siteSpec { base ->
|
||||
baseUrl = base.baseUrl + '/preview' // https://mysite.com/preview
|
||||
}
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
package com.jessebrault.ssg
|
||||
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue
|
||||
|
||||
class StaticSiteGeneratorCliIntegrationTests {
|
||||
|
||||
@Test
|
||||
@Disabled('until we figure out how to do the base dir arg')
|
||||
void defaultConfiguration() {
|
||||
def partsDir = new File('parts').tap {
|
||||
mkdir()
|
||||
deleteOnExit()
|
||||
}
|
||||
def specialPagesDir = new File('specialPages').tap {
|
||||
mkdir()
|
||||
deleteOnExit()
|
||||
}
|
||||
def templatesDir = new File('templatesDir').tap {
|
||||
mkdir()
|
||||
deleteOnExit()
|
||||
}
|
||||
def textsDir = new File('textsDir').tap {
|
||||
mkdir()
|
||||
deleteOnExit()
|
||||
}
|
||||
|
||||
new File(partsDir, 'part.gsp').write('<%= binding.test %>')
|
||||
new File(specialPagesDir, 'specialPage.gsp').write('<%= parts.part.render([test: "Greetings!"]) %>')
|
||||
new File(templatesDir, 'template.gsp').write('<%= text %>')
|
||||
new File(textsDir, 'text.md').write('---\ntemplate: template.gsp\n---\n**Hello, World!**')
|
||||
|
||||
StaticSiteGeneratorCli.main('--trace')
|
||||
|
||||
def buildDir = new File('build').tap {
|
||||
deleteOnExit()
|
||||
}
|
||||
assertTrue(buildDir.exists())
|
||||
|
||||
def textHtml = new File(buildDir, 'text.html')
|
||||
assertTrue(textHtml.exists())
|
||||
assertEquals('<p><strong>Hello, World!</strong></p>\n', textHtml.text)
|
||||
|
||||
def specialPage = new File(buildDir, 'specialPage.html')
|
||||
assertTrue(specialPage.exists())
|
||||
assertEquals('Greetings!', specialPage.text)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
package com.jessebrault.ssg
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import picocli.CommandLine
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals
|
||||
|
||||
final class StaticSiteGeneratorCliTests {
|
||||
|
||||
private static void cliSmokeScreen(String... args) {
|
||||
assertEquals(0, new CommandLine(StaticSiteGeneratorCli).with {
|
||||
caseInsensitiveEnumValuesAllowed = true
|
||||
execute(args)
|
||||
})
|
||||
}
|
||||
|
||||
@Test
|
||||
void helpSmokeScreen() {
|
||||
cliSmokeScreen('--help')
|
||||
}
|
||||
|
||||
@Test
|
||||
void initHelpSmokeScreen() {
|
||||
cliSmokeScreen('init', '--help')
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildHelpSmokeScreen() {
|
||||
cliSmokeScreen('build', '--help')
|
||||
}
|
||||
|
||||
}
|
@ -3,15 +3,16 @@
|
||||
<Appenders>
|
||||
<Console name="standard" target="SYSTEM_OUT">
|
||||
<PatternLayout>
|
||||
<MarkerPatternSelector defaultPattern="%highlight{%-5level} %logger{1}: %msg%n%ex">
|
||||
<PatternMatch key="FLOW" pattern="%highlight{%-5level} %logger{1}: %markerSimpleName %msg%n%ex" />
|
||||
<MarkerPatternSelector defaultPattern="%highlight{%-5level} %logger: %msg%n%ex">
|
||||
<PatternMatch key="FLOW" pattern="%highlight{%-5level} %logger: %markerSimpleName %msg%n%ex" />
|
||||
</MarkerPatternSelector>
|
||||
</PatternLayout>
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Root level="trace">
|
||||
<Root level="info">
|
||||
<AppenderRef ref="standard" />
|
||||
</Root>
|
||||
<Logger name="org.gradle" level="off" />
|
||||
</Loggers>
|
||||
</Configuration>
|
||||
</Configuration>
|
@ -1,11 +1,110 @@
|
||||
= com.jessebrault.ssg
|
||||
Jesse Brault
|
||||
v0.1.0
|
||||
v0.2.0
|
||||
:toc:
|
||||
:source-highlighter: rouge
|
||||
|
||||
*com.jessebrault.ssg* is a static site generator written in Groovy, giving access to the entire JVM ecosystem through its templating system.
|
||||
|
||||
== Overview
|
||||
|
||||
`ssg` has two sub-commands, `init` and `build`, one of which must be chosen when running from the command line.
|
||||
|
||||
NOTE: Previous versions of `ssg` contained a `watch` sub-command; this will be re-introduced in future versions in a more abstracted way that will handle not only the file system changes but also database/external events as well.
|
||||
|
||||
=== Sub-command: `init`
|
||||
|
||||
`init` is a simple command which creates the expected file and folder structure for `ssg` in the current directory. The resulting directories and file (only `ssgBuilds.groovy`) are empty.
|
||||
|
||||
.resulting project structure after running `init`
|
||||
[plantuml, width=25%, format=svg]
|
||||
----
|
||||
@startsalt
|
||||
{
|
||||
{T
|
||||
+ <&folder> (project directory)
|
||||
++ <&folder> pages
|
||||
++ <&folder> parts
|
||||
++ <&folder> templates
|
||||
++ <&folder> texts
|
||||
++ <&file> ssgBuilds.groovy
|
||||
}
|
||||
}
|
||||
@endsalt
|
||||
----
|
||||
|
||||
However, with the `--skeleton` option (short form `-s`), a simple text, page, template, and part are generated as well. Additionally, `ssgBuilds.groovy` contains some sample configuration for the site.
|
||||
|
||||
.resulting project structure after running `init --skeleton`
|
||||
[plantuml, width=25%, format=svg]
|
||||
----
|
||||
@startsalt
|
||||
{
|
||||
{T
|
||||
+ <&folder> (project directory)
|
||||
++ <&folder> pages
|
||||
+++ <&file> page.gsp
|
||||
++ <&folder> parts
|
||||
+++ <&file> head.gsp
|
||||
++ <&folder> templates
|
||||
+++ <&file> hello.gsp
|
||||
++ <&folder> texts
|
||||
+++ <&file> hello.md
|
||||
++ <&file> ssgBuilds.groovy
|
||||
}
|
||||
}
|
||||
@endsalt
|
||||
----
|
||||
|
||||
=== Sub-command: `build`
|
||||
|
||||
`build` encompasses the primary functionality of `ssg`. It accepts two options:
|
||||
|
||||
* `-b | --build`: The name of the build to execute. This option can be specified multiple times to specify multiple builds. The default is only one build, the `default` build; if any builds are specified, `default` is ignored (unless it is specified by the user, of course).
|
||||
* `-s | --script | --buildScript`: The path to the build script file to execute. This may only be specified once. The default is `ssgBuilds.groovy`.
|
||||
|
||||
.Examples of using `build`.
|
||||
[source,shell]
|
||||
----
|
||||
ssg build # <1>
|
||||
ssg build -b production # <2>
|
||||
ssg build -b production -b preview # <3>
|
||||
ssg build -s buildScript.groovy -b myBuild # <4>
|
||||
----
|
||||
<1> Builds the `default` build using the build script `ssgBuilds.groovy`.
|
||||
<2> Builds the `production` build using the build script `ssgBuilds.groovy`.
|
||||
<3> Builds both the `production` and `preview` builds using the build script `ssgBuilds.groovy`.
|
||||
<4> Builds the build named `myBuild` using the build script named `buildScript.groovy`.
|
||||
|
||||
== The `default` Build
|
||||
|
||||
If `init` is used to generate the project structure (or the structure is created manually by the user), the project structure matches the expected layout for the `default` build which is automatically included in all available builds. With no further specification, it will generate HTML pages from the given Texts, Pages, Templates, and Parts into the `build` directory.
|
||||
|
||||
== Program Execution
|
||||
|
||||
When `ssg` is invoked with a build file (such as `ssgBuilds.groovy` in the working directory), the following will occur:
|
||||
|
||||
. The build script is evaluated and executed, producing a collection of `Build` domain objects.
|
||||
. For each `Build` object:
|
||||
.. TaskFactories are configured using the configuration closures in the build file.
|
||||
.. TaskFactories produce Tasks, each containing all the information needed to complete the task, except for a `TaskCollection` containing all tasks.
|
||||
.. Tasks are given a `TaskCollection` and then run in parallel.
|
||||
|
||||
== The Build Script
|
||||
|
||||
The build file is evaluated as a script whose base class is `BuildScriptBase`. The script instance fields are mutated by its execution, and it (the script) is run exactly once. Each call to `build` in the script produces a new `Build` domain object which is saved by the script. The `Build` object contains all necessary data for executing that particular build:
|
||||
|
||||
* `name`: the name of the build, in order to invoke it from the command line.
|
||||
* `outDir`: the destination directory of the build, such as `build`.
|
||||
* `siteSpec`: a domain object containing the following properties which are available in all templated documents processed by the build:
|
||||
** `siteTitle`: a string.
|
||||
** `baseUrl`: a string, denoting the base url of the whole site, such as `http://example.com`, used in building absolute urls in the various templated documents.
|
||||
* `globals`: a `Map<String, Object>` containing any user-defined globals for that build.
|
||||
* `taskFactories: Closure<Void>`: a configuration block for taskFactories.
|
||||
// TODO: include what the `allBuilds` block does
|
||||
|
||||
The `Build` object also contains all the necessary configuration clousres to configure the various instances of `TaskFactory` that are used to produce instances of `Task`.
|
||||
|
||||
== Some Examples
|
||||
|
||||
.Tag Builder
|
||||
|
39
gradle/libs.versions.toml
Normal file
39
gradle/libs.versions.toml
Normal file
@ -0,0 +1,39 @@
|
||||
[versions]
|
||||
classgraph = '4.8.179'
|
||||
commonmark = '0.24.0'
|
||||
di = '0.1.0'
|
||||
fp = '0.1.0'
|
||||
groovy = '4.0.27'
|
||||
groowt = '0.1.4'
|
||||
jetbrains-annotations = '26.0.2'
|
||||
jsoup = '1.20.1'
|
||||
junit = '5.13.0'
|
||||
log4j2 = '2.24.3'
|
||||
mockito = '5.18.0'
|
||||
picocli = '4.7.7'
|
||||
slf4j = '2.0.17'
|
||||
|
||||
[libraries]
|
||||
classgraph = { module = 'io.github.classgraph:classgraph', version.ref = 'classgraph' }
|
||||
commonmark = { module = 'org.commonmark:commonmark', version.ref = 'commonmark' }
|
||||
commonmark-frontmatter = { module = 'org.commonmark:commonmark-ext-yaml-front-matter', version.ref = 'commonmark' }
|
||||
di = { module = 'com.jessebrault.di:di', version.ref = 'di' }
|
||||
fp = { module = 'com.jessebrault.fp:fp', version.ref = 'fp' }
|
||||
groovy = { module = 'org.apache.groovy:groovy', version.ref = 'groovy' }
|
||||
groovy-yaml = { module = 'org.apache.groovy:groovy-yaml', version.ref = 'groovy' }
|
||||
groowt-v = { module = 'groowt:views', version.ref = 'groowt' }
|
||||
groowt-vc = { module = 'groowt:view-components', version.ref = 'groowt' }
|
||||
groowt-wvc= { module = 'groowt:web-view-components', version.ref = 'groowt' }
|
||||
groowt-wvcc = { module = 'groowt:web-view-components-compiler', version.ref = 'groowt' }
|
||||
groowt-fp = { module = 'groowt:util-fp', version.ref = 'groowt' }
|
||||
groowt-di = { module = 'groowt:util-di', version.ref = 'groowt' }
|
||||
jetbrains-anontations = { module = 'org.jetbrains:annotations', version.ref = 'jetbrains-annotations' }
|
||||
jsoup = { module = 'org.jsoup:jsoup', version.ref = 'jsoup' }
|
||||
junit-jupiter-api = { module = 'org.junit.jupiter:junit-jupiter-api', version.ref = 'junit' }
|
||||
log4j2-api = { module = 'org.apache.logging.log4j:log4j-api', version.ref = 'log4j2' }
|
||||
log4j2-core = { module = 'org.apache.logging.log4j:log4j-core', version.ref = 'log4j2' }
|
||||
log4j2-slf4j2-impl = { module = 'org.apache.logging.log4j:log4j-slf4j2-impl', version.ref = 'log4j2' }
|
||||
mockito-core = { module = 'org.mockito:mockito-core', version.ref = 'mockito' }
|
||||
mockito-junit-jupiter = { module = 'org.mockito:mockito-junit-jupiter', version.ref = 'mockito' }
|
||||
picocli = { module = 'info.picocli:picocli', version.ref = 'picocli' }
|
||||
slf4j-api = { module = 'org.slf4j:slf4j-api', version.ref = 'slf4j' }
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
33
gradlew
vendored
33
gradlew
vendored
@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@ -55,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@ -83,10 +85,8 @@ done
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@ -133,10 +133,13 @@ location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
@ -144,7 +147,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
@ -152,7 +155,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@ -197,11 +200,15 @@ if "$cygwin" || "$msys" ; then
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
|
22
gradlew.bat
vendored
22
gradlew.bat
vendored
@ -13,6 +13,8 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
|
@ -1,22 +0,0 @@
|
||||
plugins {
|
||||
id 'ssg.common'
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/org.apache.groovy/groovy-templates
|
||||
implementation 'org.apache.groovy:groovy-templates:4.0.9'
|
||||
|
||||
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
||||
implementation 'org.commonmark:commonmark:0.21.0'
|
||||
|
||||
// https://mvnrepository.com/artifact/org.commonmark/commonmark-ext-yaml-front-matter
|
||||
implementation 'org.commonmark:commonmark-ext-yaml-front-matter:0.21.0'
|
||||
}
|
||||
|
||||
jar {
|
||||
archivesBaseName = 'ssg-lib'
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user