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.page.Page
|
||||
import groowt.util.di.RegistryObjectFactory
|
||||
import groowt.util.fp.provider.NamedSetProvider
|
||||
|
||||
import static com.jessebrault.ssg.util.ObjectUtil.*
|
||||
@ -14,8 +15,8 @@ class Build {
|
||||
final File outputDir
|
||||
final Map globals
|
||||
final Set<File> textsDirs
|
||||
final NamedSetProvider<Model> models
|
||||
final NamedSetProvider<Page> pages
|
||||
final RegistryObjectFactory objectFactory
|
||||
|
||||
Build(Map args) {
|
||||
this.name = requireString(args.name)
|
||||
@ -24,14 +25,14 @@ class Build {
|
||||
this.outputDir = requireFile(args.outputDir)
|
||||
this.globals = requireMap(args.globals)
|
||||
this.textsDirs = requireSet(args.textsDirs)
|
||||
this.models = requireType(NamedSetProvider, args.models)
|
||||
this.pages = requireType(NamedSetProvider, args.pages)
|
||||
this.objectFactory = requireType(RegistryObjectFactory, args.objectFactory)
|
||||
}
|
||||
|
||||
void doBuild() {
|
||||
// set up object factory for di
|
||||
// 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.TupleConstructor
|
||||
|
||||
import java.util.function.Supplier
|
||||
|
||||
@NullCheck
|
||||
@TupleConstructor(includeFields = true)
|
||||
class BuildDelegateToBuildSpecConverter {
|
||||
|
||||
private final FileBuildScriptGetter buildScriptGetter
|
||||
private final Supplier<BuildDelegate> buildDelegateSupplier
|
||||
|
||||
protected BuildSpec getFromDelegate(String name, BuildDelegate delegate) {
|
||||
new BuildSpec(
|
||||
@ -32,7 +35,7 @@ class BuildDelegateToBuildSpecConverter {
|
||||
extending = from.extending
|
||||
}
|
||||
|
||||
def delegate = new BuildDelegate()
|
||||
def delegate = this.buildDelegateSupplier.get()
|
||||
while (!buildHierarchy.isEmpty()) {
|
||||
def currentScript = buildHierarchy.pop()
|
||||
currentScript.buildClosure.delegate = delegate
|
||||
|
@ -2,30 +2,34 @@ package com.jessebrault.ssg.buildscript.delegates
|
||||
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import groovy.transform.NullCheck
|
||||
import groowt.util.di.DefaultRegistryObjectFactory
|
||||
import groowt.util.di.RegistryObjectFactory
|
||||
import groowt.util.fp.property.Property
|
||||
import groowt.util.fp.provider.DefaultSetProvider
|
||||
import groowt.util.fp.provider.Provider
|
||||
import groowt.util.fp.provider.SetProvider
|
||||
|
||||
import java.util.function.Supplier
|
||||
|
||||
@NullCheck(includeGenerated = true)
|
||||
@EqualsAndHashCode(includeFields = true)
|
||||
final class BuildDelegate {
|
||||
|
||||
final Property<String> siteName = Property.empty().tap {
|
||||
convention = 'An Ssg Site'
|
||||
static Supplier<BuildDelegate> withDefaults() {
|
||||
return {
|
||||
new BuildDelegate().tap {
|
||||
outputDir.convention = 'dist'
|
||||
globals.convention = [:]
|
||||
objectFactory.convention = DefaultRegistryObjectFactory.Builder.withDefaults()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Property<String> baseUrl = Property.empty().tap {
|
||||
convention = ''
|
||||
}
|
||||
|
||||
final Property<File> outputDir = Property.empty().tap {
|
||||
convention = siteName.map { it.replace(' ', '-').toLowerCase() + '-build' }
|
||||
}
|
||||
|
||||
final Property<Map<String, Object>> globals = Property.empty().tap {
|
||||
convention = [:]
|
||||
}
|
||||
final Property<String> siteName = Property.empty()
|
||||
final Property<String> baseUrl = Property.empty()
|
||||
final Property<File> outputDir = Property.empty()
|
||||
final Property<Map<String, Object>> globals = Property.empty()
|
||||
final Property<RegistryObjectFactory> objectFactory = Property.empty()
|
||||
|
||||
private final Set<Provider<File>> textsDirs = []
|
||||
|
||||
@ -53,7 +57,7 @@ final class BuildDelegate {
|
||||
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()
|
||||
globalsClosure.delegate = globalsDelegate
|
||||
globalsClosure.resolveStrategy = Closure.DELEGATE_FIRST
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.jessebrault.ssg.build
|
||||
package com.jessebrault.ssg.objects
|
||||
|
||||
import jakarta.inject.Qualifier
|
||||
|
||||
@ -10,7 +10,7 @@ import java.lang.annotation.Target
|
||||
@Qualifier
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target([ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD])
|
||||
@interface Page {
|
||||
@interface InjectPage {
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@ -10,7 +10,7 @@ import java.lang.annotation.Target
|
||||
@Qualifier
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target([ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD])
|
||||
@interface Pages {
|
||||
@interface InjectPages {
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@ -10,7 +10,7 @@ import java.lang.annotation.Target
|
||||
@Qualifier
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target([ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD])
|
||||
@interface Text {
|
||||
@interface InjectText {
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@ -10,7 +10,7 @@ import java.lang.annotation.Target
|
||||
@Qualifier
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target([ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD])
|
||||
@interface Texts {
|
||||
@interface InjectTexts {
|
||||
|
||||
/**
|
||||
* 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