Working on Build and ssg object factory.
This commit is contained in:
parent
76c6280b5d
commit
140dffefc6
@ -2,6 +2,7 @@ package com.jessebrault.ssg.build
|
|||||||
|
|
||||||
import com.jessebrault.ssg.model.Model
|
import com.jessebrault.ssg.model.Model
|
||||||
import com.jessebrault.ssg.page.Page
|
import com.jessebrault.ssg.page.Page
|
||||||
|
import groowt.util.di.RegistryObjectFactory
|
||||||
import groowt.util.fp.provider.NamedSetProvider
|
import groowt.util.fp.provider.NamedSetProvider
|
||||||
|
|
||||||
import static com.jessebrault.ssg.util.ObjectUtil.*
|
import static com.jessebrault.ssg.util.ObjectUtil.*
|
||||||
@ -14,8 +15,8 @@ class Build {
|
|||||||
final File outputDir
|
final File outputDir
|
||||||
final Map globals
|
final Map globals
|
||||||
final Set<File> textsDirs
|
final Set<File> textsDirs
|
||||||
final NamedSetProvider<Model> models
|
|
||||||
final NamedSetProvider<Page> pages
|
final NamedSetProvider<Page> pages
|
||||||
|
final RegistryObjectFactory objectFactory
|
||||||
|
|
||||||
Build(Map args) {
|
Build(Map args) {
|
||||||
this.name = requireString(args.name)
|
this.name = requireString(args.name)
|
||||||
@ -24,14 +25,14 @@ class Build {
|
|||||||
this.outputDir = requireFile(args.outputDir)
|
this.outputDir = requireFile(args.outputDir)
|
||||||
this.globals = requireMap(args.globals)
|
this.globals = requireMap(args.globals)
|
||||||
this.textsDirs = requireSet(args.textsDirs)
|
this.textsDirs = requireSet(args.textsDirs)
|
||||||
this.models = requireType(NamedSetProvider, args.models)
|
|
||||||
this.pages = requireType(NamedSetProvider, args.pages)
|
this.pages = requireType(NamedSetProvider, args.pages)
|
||||||
|
this.objectFactory = requireType(RegistryObjectFactory, args.objectFactory)
|
||||||
}
|
}
|
||||||
|
|
||||||
void doBuild() {
|
void doBuild() {
|
||||||
// set up object factory for di
|
// set up object factory for di
|
||||||
// container should have: Build and all its properties
|
// container should have: Build and all its properties
|
||||||
// container should also have @Text, @Texts, @Model, @Models, and @Page resolvers
|
// container should also have @Text, @Texts, @Page, and @Pages resolvers
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,14 @@ import com.jessebrault.ssg.buildscript.delegates.BuildDelegate
|
|||||||
import groovy.transform.NullCheck
|
import groovy.transform.NullCheck
|
||||||
import groovy.transform.TupleConstructor
|
import groovy.transform.TupleConstructor
|
||||||
|
|
||||||
|
import java.util.function.Supplier
|
||||||
|
|
||||||
@NullCheck
|
@NullCheck
|
||||||
@TupleConstructor(includeFields = true)
|
@TupleConstructor(includeFields = true)
|
||||||
class BuildDelegateToBuildSpecConverter {
|
class BuildDelegateToBuildSpecConverter {
|
||||||
|
|
||||||
private final FileBuildScriptGetter buildScriptGetter
|
private final FileBuildScriptGetter buildScriptGetter
|
||||||
|
private final Supplier<BuildDelegate> buildDelegateSupplier
|
||||||
|
|
||||||
protected BuildSpec getFromDelegate(String name, BuildDelegate delegate) {
|
protected BuildSpec getFromDelegate(String name, BuildDelegate delegate) {
|
||||||
new BuildSpec(
|
new BuildSpec(
|
||||||
@ -32,7 +35,7 @@ class BuildDelegateToBuildSpecConverter {
|
|||||||
extending = from.extending
|
extending = from.extending
|
||||||
}
|
}
|
||||||
|
|
||||||
def delegate = new BuildDelegate()
|
def delegate = this.buildDelegateSupplier.get()
|
||||||
while (!buildHierarchy.isEmpty()) {
|
while (!buildHierarchy.isEmpty()) {
|
||||||
def currentScript = buildHierarchy.pop()
|
def currentScript = buildHierarchy.pop()
|
||||||
currentScript.buildClosure.delegate = delegate
|
currentScript.buildClosure.delegate = delegate
|
||||||
|
@ -2,30 +2,34 @@ package com.jessebrault.ssg.buildscript.delegates
|
|||||||
|
|
||||||
import groovy.transform.EqualsAndHashCode
|
import groovy.transform.EqualsAndHashCode
|
||||||
import groovy.transform.NullCheck
|
import groovy.transform.NullCheck
|
||||||
|
import groowt.util.di.DefaultRegistryObjectFactory
|
||||||
|
import groowt.util.di.RegistryObjectFactory
|
||||||
import groowt.util.fp.property.Property
|
import groowt.util.fp.property.Property
|
||||||
import groowt.util.fp.provider.DefaultSetProvider
|
import groowt.util.fp.provider.DefaultSetProvider
|
||||||
import groowt.util.fp.provider.Provider
|
import groowt.util.fp.provider.Provider
|
||||||
import groowt.util.fp.provider.SetProvider
|
import groowt.util.fp.provider.SetProvider
|
||||||
|
|
||||||
|
import java.util.function.Supplier
|
||||||
|
|
||||||
@NullCheck(includeGenerated = true)
|
@NullCheck(includeGenerated = true)
|
||||||
@EqualsAndHashCode(includeFields = true)
|
@EqualsAndHashCode(includeFields = true)
|
||||||
final class BuildDelegate {
|
final class BuildDelegate {
|
||||||
|
|
||||||
final Property<String> siteName = Property.empty().tap {
|
static Supplier<BuildDelegate> withDefaults() {
|
||||||
convention = 'An Ssg Site'
|
return {
|
||||||
|
new BuildDelegate().tap {
|
||||||
|
outputDir.convention = 'dist'
|
||||||
|
globals.convention = [:]
|
||||||
|
objectFactory.convention = DefaultRegistryObjectFactory.Builder.withDefaults()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final Property<String> baseUrl = Property.empty().tap {
|
final Property<String> siteName = Property.empty()
|
||||||
convention = ''
|
final Property<String> baseUrl = Property.empty()
|
||||||
}
|
final Property<File> outputDir = Property.empty()
|
||||||
|
final Property<Map<String, Object>> globals = Property.empty()
|
||||||
final Property<File> outputDir = Property.empty().tap {
|
final Property<RegistryObjectFactory> objectFactory = Property.empty()
|
||||||
convention = siteName.map { it.replace(' ', '-').toLowerCase() + '-build' }
|
|
||||||
}
|
|
||||||
|
|
||||||
final Property<Map<String, Object>> globals = Property.empty().tap {
|
|
||||||
convention = [:]
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Set<Provider<File>> textsDirs = []
|
private final Set<Provider<File>> textsDirs = []
|
||||||
|
|
||||||
@ -53,7 +57,7 @@ final class BuildDelegate {
|
|||||||
this.outputDir.set(outputDirProvider)
|
this.outputDir.set(outputDirProvider)
|
||||||
}
|
}
|
||||||
|
|
||||||
void globals(@DelegatesTo(value = GlobalsDelegate, strategy = Closure.DELEGATE_FIRST) Closure<?> globalsClosure) {
|
void globals(@DelegatesTo(value = GlobalsDelegate, strategy = Closure.DELEGATE_FIRST) Closure globalsClosure) {
|
||||||
def globalsDelegate = new GlobalsDelegate()
|
def globalsDelegate = new GlobalsDelegate()
|
||||||
globalsClosure.delegate = globalsDelegate
|
globalsClosure.delegate = globalsDelegate
|
||||||
globalsClosure.resolveStrategy = Closure.DELEGATE_FIRST
|
globalsClosure.resolveStrategy = Closure.DELEGATE_FIRST
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package com.jessebrault.ssg.build
|
package com.jessebrault.ssg.objects
|
||||||
|
|
||||||
import jakarta.inject.Qualifier
|
import jakarta.inject.Qualifier
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ import java.lang.annotation.Target
|
|||||||
@Qualifier
|
@Qualifier
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target([ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD])
|
@Target([ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD])
|
||||||
@interface Page {
|
@interface InjectPage {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* May be either a page name or a path starting with '/'
|
* May be either a page name or a path starting with '/'
|
@ -0,0 +1,33 @@
|
|||||||
|
package com.jessebrault.ssg.objects
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.page.Page
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
import groowt.util.di.Binding
|
||||||
|
import groowt.util.di.QualifierHandler
|
||||||
|
import groowt.util.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.pageProviders.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.get() as T)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package com.jessebrault.ssg.build
|
package com.jessebrault.ssg.objects
|
||||||
|
|
||||||
import jakarta.inject.Qualifier
|
import jakarta.inject.Qualifier
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ import java.lang.annotation.Target
|
|||||||
@Qualifier
|
@Qualifier
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target([ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD])
|
@Target([ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD])
|
||||||
@interface Pages {
|
@interface InjectPages {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Names of pages and/or globs (starting with '/') of pages
|
* Names of pages and/or globs (starting with '/') of pages
|
@ -0,0 +1,44 @@
|
|||||||
|
package com.jessebrault.ssg.objects
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.page.Page
|
||||||
|
import com.jessebrault.ssg.util.Glob
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
import groowt.util.di.Binding
|
||||||
|
import groowt.util.di.QualifierHandler
|
||||||
|
import groowt.util.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.pageProviders.inject([] as Set<Page>) { acc, pageProvider ->
|
||||||
|
if (glob.matches(pageProvider.path)) {
|
||||||
|
acc << pageProvider.get()
|
||||||
|
}
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
allFound.each { foundPages << it }
|
||||||
|
} else {
|
||||||
|
def found = this.pagesExtension.pageProviders.find { it.name == requested }
|
||||||
|
if (found == null) {
|
||||||
|
throw new IllegalArgumentException("Cannot find page with the name: $requested")
|
||||||
|
}
|
||||||
|
foundPages << found.get()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new SingletonBinding<T>(foundPages as T)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package com.jessebrault.ssg.build
|
package com.jessebrault.ssg.objects
|
||||||
|
|
||||||
import jakarta.inject.Qualifier
|
import jakarta.inject.Qualifier
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ import java.lang.annotation.Target
|
|||||||
@Qualifier
|
@Qualifier
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target([ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD])
|
@Target([ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD])
|
||||||
@interface Text {
|
@interface InjectText {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the text, or the path of the text, starting with '/'
|
* The name of the text, or the path of the text, starting with '/'
|
@ -1,4 +1,4 @@
|
|||||||
package com.jessebrault.ssg.build
|
package com.jessebrault.ssg.objects
|
||||||
|
|
||||||
import jakarta.inject.Qualifier
|
import jakarta.inject.Qualifier
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ import java.lang.annotation.Target
|
|||||||
@Qualifier
|
@Qualifier
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target([ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD])
|
@Target([ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD])
|
||||||
@interface Texts {
|
@interface InjectTexts {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Names of texts and/or globs (starting with '/') of texts
|
* Names of texts and/or globs (starting with '/') of texts
|
@ -0,0 +1,28 @@
|
|||||||
|
package com.jessebrault.ssg.objects
|
||||||
|
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.provider.PageProvider
|
||||||
|
import groowt.util.di.QualifierHandler
|
||||||
|
import groowt.util.di.QualifierHandlerContainer
|
||||||
|
import groowt.util.di.RegistryExtension
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation
|
||||||
|
|
||||||
|
class PagesExtension implements QualifierHandlerContainer, RegistryExtension {
|
||||||
|
|
||||||
|
final Set<PageProvider> pageProviders = []
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package com.jessebrault.ssg.objects
|
||||||
|
|
||||||
|
import groowt.util.di.DefaultRegistryObjectFactory
|
||||||
|
import groowt.util.di.RegistryObjectFactory
|
||||||
|
|
||||||
|
final class SsgObjectFactory {
|
||||||
|
|
||||||
|
static RegistryObjectFactory getDefault() {
|
||||||
|
DefaultRegistryObjectFactory.Builder.withDefaults().with {
|
||||||
|
it.configureRegistry { registry ->
|
||||||
|
registry.addExtension(new PagesExtension())
|
||||||
|
}
|
||||||
|
build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private SsgObjectFactory() {}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.jessebrault.ssg.provider
|
||||||
|
|
||||||
|
import com.jessebrault.ssg.page.Page
|
||||||
|
import groovy.transform.TupleConstructor
|
||||||
|
import groowt.util.fp.provider.NamedProvider
|
||||||
|
import groowt.util.fp.provider.Provider
|
||||||
|
|
||||||
|
@TupleConstructor(includeFields = true, force = true, defaults = false)
|
||||||
|
class PageProvider implements NamedProvider<Page> {
|
||||||
|
|
||||||
|
final String name
|
||||||
|
final String path
|
||||||
|
|
||||||
|
private final Provider<Page> lazyPage
|
||||||
|
|
||||||
|
PageProvider(String name, String path, Page page) {
|
||||||
|
this.name = name
|
||||||
|
this.path = path
|
||||||
|
this.lazyPage = { page }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Page get() {
|
||||||
|
this.lazyPage.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
119
api/src/main/groovy/com/jessebrault/ssg/util/Glob.groovy
Normal file
119
api/src/main/groovy/com/jessebrault/ssg/util/Glob.groovy
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
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>
|
||||||
|
}
|
||||||
|
|
||||||
|
def subjectPartIter = subjectParts.iterator()
|
||||||
|
def subjectPartStack = new LinkedList<String>()
|
||||||
|
while (subjectPartIter.hasNext()) {
|
||||||
|
subjectPartStack.push(subjectPartIter.next())
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean result = true
|
||||||
|
parts:
|
||||||
|
for (def part : this.parts) {
|
||||||
|
switch (part) {
|
||||||
|
case Literal -> {
|
||||||
|
if (subjectPartStack.isEmpty()) {
|
||||||
|
result = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
def subjectPart = subjectPartStack.pop()
|
||||||
|
if (part.literal != subjectPart) {
|
||||||
|
result = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case AnyDirectoryHierarchy -> {
|
||||||
|
while (!subjectPartStack.isEmpty()) {
|
||||||
|
def current = subjectPartStack.pop()
|
||||||
|
if (subjectPartStack.isEmpty()) {
|
||||||
|
subjectPartStack.push(current)
|
||||||
|
continue parts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case GlobFileOrDirectory -> {
|
||||||
|
def subjectPart = subjectPartStack.pop()
|
||||||
|
def m = part.regexPattern.matcher(subjectPart)
|
||||||
|
if (!m.matches()) {
|
||||||
|
result = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user