Overhaul for 0.7.0-SNAPSHOT.
This commit is contained in:
parent
52dc26c450
commit
ba821b11c2
@ -8,7 +8,8 @@ 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`.
|
||||
`cli` and `api` projects in `ssg-gradle-plugin/src/main/java/com/jessebrault/ssg/gradle/SsgGradlePlugin.java`. Finally,
|
||||
update the version in the `cli` project for the cli info message.
|
||||
|
||||
## Publishing
|
||||
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
package com.jessebrault.ssg;
|
||||
|
||||
import groowt.view.component.web.WebViewComponent;
|
||||
import io.github.classgraph.ScanResult;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface ComponentClassScanner {
|
||||
Set<Class<? extends WebViewComponent>> getWebViewComponentClasses(ScanResult scanResult);
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package com.jessebrault.ssg;
|
||||
|
||||
import com.jessebrault.ssg.page.Page;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class ConsolePageWriter implements PageWriter {
|
||||
|
||||
@Override
|
||||
public void write(Page page, File outputDir, String renderedPage) {
|
||||
System.out.println("--- Page " + page.getPath() + " ---");
|
||||
System.out.println(renderedPage);
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,41 @@
|
||||
package com.jessebrault.ssg;
|
||||
|
||||
import groowt.view.component.web.WebViewComponent;
|
||||
import io.github.classgraph.ClassInfoList;
|
||||
import io.github.classgraph.ScanResult;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
public class DefaultComponentClassScanner implements ComponentClassScanner {
|
||||
|
||||
private final ExecutorService executorService;
|
||||
|
||||
@Inject
|
||||
public DefaultComponentClassScanner(ExecutorService executorService) {
|
||||
this.executorService = executorService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Class<? extends WebViewComponent>> getWebViewComponentClasses(ScanResult scanResult) {
|
||||
final ClassInfoList classInfoList = scanResult.getClassesImplementing(WebViewComponent.class);
|
||||
final Set<Class<? extends WebViewComponent>> results = ConcurrentHashMap.newKeySet();
|
||||
|
||||
// fork
|
||||
final List<CompletableFuture<Void>> futures = classInfoList.stream().map(classInfo ->
|
||||
CompletableFuture.runAsync(() ->
|
||||
results.add(classInfo.loadClass(WebViewComponent.class)),
|
||||
executorService
|
||||
)).toList();
|
||||
|
||||
// join
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
package com.jessebrault.ssg;
|
||||
|
||||
import com.jessebrault.di.RegistryObjectFactory;
|
||||
import com.jessebrault.ssg.buildscript.BuildSpec;
|
||||
import com.jessebrault.ssg.di.GlobalsExtension;
|
||||
import com.jessebrault.ssg.di.ModelsExtension;
|
||||
import com.jessebrault.ssg.di.TextsExtension;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import static com.jessebrault.di.BindingUtil.named;
|
||||
import static com.jessebrault.di.BindingUtil.toSingleton;
|
||||
|
||||
public class DefaultObjectFactoryConfigurator implements ObjectFactoryConfigurator {
|
||||
|
||||
private final TextsGetter textsGetter;
|
||||
|
||||
@Inject
|
||||
public DefaultObjectFactoryConfigurator(TextsGetter textsGetter) {
|
||||
this.textsGetter = textsGetter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(RegistryObjectFactory registryObjectFactory, BuildSpec buildSpec) {
|
||||
registryObjectFactory.configureRegistry(registry -> {
|
||||
// texts
|
||||
final var textsExtension = new TextsExtension();
|
||||
textsExtension.getAllTexts().addAll(this.textsGetter.getTexts(buildSpec));
|
||||
registry.addExtension(textsExtension);
|
||||
|
||||
// models
|
||||
final var modelsExtension = new ModelsExtension();
|
||||
modelsExtension.getAllModels().addAll(buildSpec.getModels().get(() ->
|
||||
new SsgException("the models Property in " + buildSpec.getName()
|
||||
+ " must contain at least an empty set.")
|
||||
));
|
||||
registry.addExtension(modelsExtension);
|
||||
|
||||
// globals
|
||||
final var globalsExtension = new GlobalsExtension();
|
||||
globalsExtension.getGlobals().putAll(buildSpec.getGlobals().get(() ->
|
||||
new SsgException("the globals Property in " + buildSpec.getName()
|
||||
+ " must contain at least an empty set.")
|
||||
));
|
||||
registry.addExtension(globalsExtension);
|
||||
|
||||
// various others
|
||||
registry.bind(named("buildName", String.class), toSingleton(buildSpec.getName()));
|
||||
registry.bind(named("siteName", String.class), toSingleton(buildSpec.getSiteName().get(() ->
|
||||
new SsgException("the siteName Property in " + buildSpec.getName() + " must be set.")
|
||||
)));
|
||||
registry.bind(named("baseUrl", String.class), toSingleton(buildSpec.getBaseUrl().get(() ->
|
||||
new SsgException("the baseUrl Property in " + buildSpec.getName() + " must be set.")
|
||||
)));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
package com.jessebrault.ssg
|
||||
|
||||
import com.jessebrault.di.ObjectFactory
|
||||
import com.jessebrault.ssg.view.SkipTemplate
|
||||
import com.jessebrault.ssg.view.WvcCompiler
|
||||
import com.jessebrault.ssg.view.WvcPageView
|
||||
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
|
||||
|
||||
class DefaultPageContextFactory implements PageContextFactory {
|
||||
|
||||
protected WebViewComponent makeComponent(
|
||||
ObjectFactory objectFactory,
|
||||
Class<? extends WebViewComponent> wvcClass,
|
||||
Map attr,
|
||||
Object[] args
|
||||
) {
|
||||
if (!attr.isEmpty() && args.length > 0) {
|
||||
return objectFactory.createInstance(wvcClass, attr, *args)
|
||||
} else if (!attr.isEmpty()) {
|
||||
return objectFactory.createInstance(wvcClass, attr)
|
||||
} else if (args.length > 0) {
|
||||
return objectFactory.createInstance(wvcClass, *args)
|
||||
} else {
|
||||
return objectFactory.createInstance(wvcClass)
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
WebViewComponentContext makeContext(
|
||||
WvcPageView wvcPageView,
|
||||
ObjectFactory buildObjectFactory,
|
||||
Set<Class<? extends WebViewComponent>> allWvcClasses
|
||||
) {
|
||||
new DefaultWebViewComponentContext().tap {
|
||||
configureRootScope(WebViewComponentScope) {
|
||||
// custom components
|
||||
allWvcClasses.each { wvcClass ->
|
||||
//noinspection GroovyAssignabilityCheck
|
||||
add(wvcClass, ComponentFactories.ofClosureClassType(wvcClass) { Map attr, Object[] args ->
|
||||
// instantiate component and set context
|
||||
WebViewComponent component = makeComponent(buildObjectFactory, wvcClass, attr, args)
|
||||
component.context = wvcPageView.context
|
||||
|
||||
// set the template
|
||||
if (component.componentTemplate == null && !wvcClass.isAnnotationPresent(SkipTemplate)) {
|
||||
def compileResult = buildObjectFactory.createInstance(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
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
package com.jessebrault.ssg;
|
||||
|
||||
import com.jessebrault.di.ObjectFactory;
|
||||
import com.jessebrault.fp.either.Either;
|
||||
import com.jessebrault.ssg.page.Page;
|
||||
import com.jessebrault.ssg.util.Diagnostic;
|
||||
import com.jessebrault.ssg.view.PageView;
|
||||
import com.jessebrault.ssg.view.WvcPageView;
|
||||
import groowt.view.component.web.WebViewComponent;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.io.StringWriter;
|
||||
import java.util.Set;
|
||||
|
||||
public class DefaultPageRenderer implements PageRenderer {
|
||||
|
||||
private final PageContextFactory pageContextFactory;
|
||||
|
||||
@Inject
|
||||
public DefaultPageRenderer(PageContextFactory pageContextFactory) {
|
||||
this.pageContextFactory = pageContextFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Either<Diagnostic, String> renderPage(
|
||||
Page page,
|
||||
String baseUrl,
|
||||
ObjectFactory buildObjectFactory,
|
||||
Set<Class<? extends WebViewComponent>> allWvcClasses
|
||||
) {
|
||||
// create the view
|
||||
final Either<Diagnostic, PageView> viewResult = page.createView();
|
||||
if (viewResult.isLeft()) {
|
||||
return Either.left(viewResult.getLeft());
|
||||
}
|
||||
final PageView pageView = viewResult.getRight();
|
||||
|
||||
// prepare for rendering
|
||||
// set props
|
||||
pageView.setPageTitle(page.getName());
|
||||
pageView.setUrl(baseUrl + page.getPath());
|
||||
|
||||
// set context if WvcPageView
|
||||
if (pageView instanceof WvcPageView wvcPageView) {
|
||||
wvcPageView.setContext(this.pageContextFactory.makeContext(wvcPageView, buildObjectFactory, allWvcClasses));
|
||||
}
|
||||
|
||||
// Render page
|
||||
final var sw = new StringWriter();
|
||||
try {
|
||||
pageView.renderTo(sw);
|
||||
} catch (Exception exception) {
|
||||
return Either.left(new Diagnostic(
|
||||
"There was an exception while rendering " + page.getName() + " as " + pageView.getClass().getName(),
|
||||
exception
|
||||
));
|
||||
}
|
||||
|
||||
return Either.right(sw.toString());
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,80 @@
|
||||
package com.jessebrault.ssg;
|
||||
|
||||
import com.jessebrault.di.ObjectFactory;
|
||||
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.view.PageView;
|
||||
import com.jessebrault.ssg.view.WvcCompiler;
|
||||
import io.github.classgraph.AnnotationInfo;
|
||||
import io.github.classgraph.ClassInfoList;
|
||||
import io.github.classgraph.ScanResult;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class DefaultPageScanner implements PageScanner {
|
||||
|
||||
private final ExecutorService executorService;
|
||||
private final WvcCompiler wvcCompiler;
|
||||
|
||||
@Inject
|
||||
public DefaultPageScanner(ExecutorService executorService, WvcCompiler wvcCompiler) {
|
||||
this.executorService = executorService;
|
||||
this.wvcCompiler = wvcCompiler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Page> getAllPages(ScanResult scanResult, ObjectFactory buildObjectFactory) {
|
||||
final Set<Page> results = ConcurrentHashMap.newKeySet();
|
||||
|
||||
// Start fetching single pages
|
||||
final ClassInfoList pageViewInfoList = scanResult.getClassesImplementing(PageView.class);
|
||||
final List<CompletableFuture<Void>> pageViewFutures = pageViewInfoList.stream()
|
||||
.map(classInfo -> {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
final AnnotationInfo annotationInfo = classInfo.getAnnotationInfo(PageSpec.class);
|
||||
if (annotationInfo != null) {
|
||||
final PageSpec pageSpec = (PageSpec) annotationInfo.loadClassAndInstantiate();
|
||||
results.add(new DefaultWvcPage(Map.of(
|
||||
"name", pageSpec.name(),
|
||||
"path", pageSpec.path(),
|
||||
"fileExtension", pageSpec.fileExtension(),
|
||||
"viewType", classInfo.loadClass(),
|
||||
"templateResource", pageSpec.templateResource().isEmpty()
|
||||
? classInfo.getSimpleName() + "Template.wvc"
|
||||
: pageSpec.templateResource(),
|
||||
"objectFactory", buildObjectFactory,
|
||||
"wvcCompiler", this.wvcCompiler
|
||||
)));
|
||||
}
|
||||
}, this.executorService);
|
||||
})
|
||||
.toList();
|
||||
|
||||
// Start fetching page factories
|
||||
final ClassInfoList pageFactoryInfoList = scanResult.getClassesImplementing(PageFactory.class);
|
||||
final List<CompletableFuture<Void>> pageFactoryFutures = pageFactoryInfoList.stream()
|
||||
.map(classInfo -> {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
final Class<? extends PageFactory> pageFactoryClass = classInfo.loadClass(PageFactory.class);
|
||||
final PageFactory pageFactory = buildObjectFactory.createInstance(pageFactoryClass);
|
||||
final Collection<Page> pages = pageFactory.create();
|
||||
results.addAll(pages);
|
||||
}, this.executorService);
|
||||
})
|
||||
.toList();
|
||||
|
||||
// join
|
||||
final List<CompletableFuture<Void>> allFutures = new ArrayList<>();
|
||||
allFutures.addAll(pageViewFutures);
|
||||
allFutures.addAll(pageFactoryFutures);
|
||||
|
||||
CompletableFuture.allOf(allFutures.toArray(new CompletableFuture[0])).join();
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
}
|
||||
@ -183,8 +183,6 @@ class DefaultStaticSiteGenerator implements StaticSiteGenerator {
|
||||
|
||||
@Override
|
||||
Collection<Diagnostic> doBuild(
|
||||
File projectDir,
|
||||
String buildName,
|
||||
String buildScriptFqn,
|
||||
Map<String, String> buildScriptCliArgs
|
||||
) {
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
package com.jessebrault.ssg;
|
||||
|
||||
import com.jessebrault.ssg.buildscript.BuildSpec;
|
||||
import com.jessebrault.ssg.text.Text;
|
||||
import com.jessebrault.ssg.text.TextSupplier;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class DefaultTextsGetter implements TextsGetter {
|
||||
|
||||
@Override
|
||||
public Set<Text> getTexts(BuildSpec buildSpec) {
|
||||
final Set<TextSupplier> textSuppliers = buildSpec.getTextSuppliers().get(() ->
|
||||
new SsgException("The textSuppliers Property in " + buildSpec.getName()
|
||||
+ " must contain at least an empty Set.")
|
||||
);
|
||||
final Set<Text> texts = new HashSet<>();
|
||||
for (final var textSupplier : textSuppliers) {
|
||||
texts.addAll(textSupplier.get());
|
||||
}
|
||||
return texts;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package com.jessebrault.ssg;
|
||||
|
||||
import com.jessebrault.ssg.view.WvcCompiler;
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import groowt.view.component.compiler.SimpleComponentTemplateClassFactory;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
public class DefaultWvcCompilerFactory implements WvcCompilerFactory {
|
||||
|
||||
private final GroovyClassLoader groovyClassLoader;
|
||||
|
||||
@Inject
|
||||
public DefaultWvcCompilerFactory(GroovyClassLoader groovyClassLoader) {
|
||||
this.groovyClassLoader = groovyClassLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public WvcCompiler getWvcCompiler() {
|
||||
return new WvcCompiler(this.groovyClassLoader, new SimpleComponentTemplateClassFactory(this.groovyClassLoader));
|
||||
}
|
||||
|
||||
}
|
||||
43
api/src/main/groovy/com/jessebrault/ssg/FilePageWriter.java
Normal file
43
api/src/main/groovy/com/jessebrault/ssg/FilePageWriter.java
Normal file
@ -0,0 +1,43 @@
|
||||
package com.jessebrault.ssg;
|
||||
|
||||
import com.jessebrault.ssg.page.Page;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class FilePageWriter implements PageWriter {
|
||||
|
||||
@Override
|
||||
public void write(Page page, File outputDir, String renderedPage) {
|
||||
if (!outputDir.mkdirs()) {
|
||||
throw new RuntimeException("Could not make directories for outputDir " + outputDir);
|
||||
};
|
||||
|
||||
// calculate target path
|
||||
final List<String> pathParts = Arrays.asList(page.getPath().split("/"));
|
||||
if (page.getPath().endsWith("/")) {
|
||||
pathParts.add("index");
|
||||
}
|
||||
|
||||
final String head = pathParts.getFirst();
|
||||
final List<String> tail = pathParts.size() > 1 ? pathParts.subList(1, pathParts.size()) : List.of();
|
||||
|
||||
final Path path = Path.of(head, tail.toArray(String[]::new));
|
||||
final File outputFile = new File(outputDir, path + page.getFileExtension());
|
||||
|
||||
// make dirs and write
|
||||
if (!outputFile.getParentFile().mkdirs()) {
|
||||
throw new RuntimeException("Could not make parent directories for " + outputFile);
|
||||
}
|
||||
try (final FileWriter writer = new FileWriter(outputFile)) {
|
||||
writer.write(renderedPage);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,140 @@
|
||||
package com.jessebrault.ssg;
|
||||
|
||||
import com.jessebrault.di.BindingUtil;
|
||||
import com.jessebrault.di.RegistryObjectFactory;
|
||||
import com.jessebrault.fp.either.Either;
|
||||
import com.jessebrault.ssg.buildscript.BuildSpec;
|
||||
import com.jessebrault.ssg.buildscript.BuildSpecFactory;
|
||||
import com.jessebrault.ssg.di.PagesExtension;
|
||||
import com.jessebrault.ssg.di.SelfPageExtension;
|
||||
import com.jessebrault.ssg.page.Page;
|
||||
import com.jessebrault.ssg.util.Diagnostic;
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import groowt.view.component.web.WebViewComponent;
|
||||
import io.github.classgraph.ClassGraph;
|
||||
import io.github.classgraph.ScanResult;
|
||||
import jakarta.inject.Inject;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
|
||||
public class JDefaultStaticSiteGenerator implements StaticSiteGenerator {
|
||||
|
||||
private final BuildSpecFactory buildSpecFactory;
|
||||
private final ObjectFactoryConfigurator objectFactoryConfigurator;
|
||||
private final GroovyClassLoader groovyClassLoader;
|
||||
private final ExecutorService executorService;
|
||||
private final PageScanner pageScanner;
|
||||
private final ComponentClassScanner componentClassScanner;
|
||||
private final PageRenderer pageRenderer;
|
||||
private final PageWriter pageWriter;
|
||||
|
||||
@Inject
|
||||
public JDefaultStaticSiteGenerator(
|
||||
BuildSpecFactory buildSpecFactory,
|
||||
ObjectFactoryConfigurator objectFactoryConfigurator,
|
||||
GroovyClassLoader groovyClassLoader,
|
||||
ExecutorService executorService,
|
||||
PageScanner pageScanner,
|
||||
ComponentClassScanner componentClassScanner,
|
||||
PageRenderer pageRenderer,
|
||||
PageWriter pageWriter
|
||||
) {
|
||||
this.buildSpecFactory = buildSpecFactory;
|
||||
this.objectFactoryConfigurator = objectFactoryConfigurator;
|
||||
this.groovyClassLoader = groovyClassLoader;
|
||||
this.executorService = executorService;
|
||||
this.pageScanner = pageScanner;
|
||||
this.componentClassScanner = componentClassScanner;
|
||||
this.pageRenderer = pageRenderer;
|
||||
this.pageWriter = pageWriter;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Collection<Diagnostic> doBuild(String buildScriptFqn, Map<String, String> buildScriptCliArgs) {
|
||||
// Get build spec
|
||||
final BuildSpec buildSpec = this.buildSpecFactory.getBuildSpec(buildScriptFqn, buildScriptCliArgs);
|
||||
|
||||
// Prepare object factory for rendering pages and components
|
||||
final RegistryObjectFactory buildObjectFactory = buildSpec.getObjectFactory().get(() ->
|
||||
new SsgException("objectFactory Provider in " + buildSpec.getName() + " must be set.")
|
||||
);
|
||||
this.objectFactoryConfigurator.configure(buildObjectFactory, buildSpec);
|
||||
|
||||
// ClassGraph scan of base packages
|
||||
final Set<String> basePackages = buildSpec.getBasePackages().get(() ->
|
||||
new SsgException("basePackages Provider in " + buildSpec.getName() + " must be at least an empty Set.")
|
||||
);
|
||||
final ClassGraph classGraph = new ClassGraph()
|
||||
.enableAnnotationInfo()
|
||||
.addClassLoader(this.groovyClassLoader);
|
||||
for (final String basePackage : basePackages) {
|
||||
classGraph.acceptPackages(basePackage);
|
||||
}
|
||||
|
||||
// Get all pages and components from scan
|
||||
final Set<Page> pages = ConcurrentHashMap.newKeySet();
|
||||
final Set<Class<? extends WebViewComponent>> componentClasses = ConcurrentHashMap.newKeySet();
|
||||
|
||||
try (final ScanResult scanResult = classGraph.scan()) {
|
||||
// fork
|
||||
final CompletableFuture<Void> pagesFuture = CompletableFuture.runAsync(
|
||||
() -> pages.addAll(this.pageScanner.getAllPages(scanResult, buildObjectFactory)),
|
||||
this.executorService
|
||||
);
|
||||
final CompletableFuture<Void> componentsFuture = CompletableFuture.runAsync(
|
||||
() -> componentClasses.addAll(this.componentClassScanner.getWebViewComponentClasses(scanResult)),
|
||||
this.executorService
|
||||
);
|
||||
|
||||
// join
|
||||
CompletableFuture.allOf(pagesFuture, componentsFuture).join();
|
||||
}
|
||||
|
||||
// final ObjectFactory configuration to add all pages/components found AND self page extension
|
||||
buildObjectFactory.configureRegistry(registry -> {
|
||||
final var pagesExtension = new PagesExtension();
|
||||
pagesExtension.getAllPages().addAll(pages);
|
||||
registry.addExtension(pagesExtension);
|
||||
|
||||
registry.bind(BindingUtil.named("allWvc", Set.class), BindingUtil.toSingleton(componentClasses));
|
||||
|
||||
registry.addExtension(new SelfPageExtension());
|
||||
});
|
||||
|
||||
// render each page
|
||||
final Set<Diagnostic> diagnostics = ConcurrentHashMap.newKeySet();
|
||||
|
||||
final List<CompletableFuture<Void>> renderAndWriteFutures = pages.stream()
|
||||
.map(page -> CompletableFuture.runAsync(() -> {
|
||||
final Either<Diagnostic, String> renderResult = this.pageRenderer.renderPage(
|
||||
page,
|
||||
buildSpec.getBaseUrl().get(() ->
|
||||
new SsgException("baseUrl Provider in " + buildSpec.getName() + " must be set.")
|
||||
),
|
||||
buildObjectFactory,
|
||||
componentClasses
|
||||
);
|
||||
if (renderResult.isLeft()) {
|
||||
diagnostics.add(renderResult.getLeft());
|
||||
return;
|
||||
}
|
||||
this.pageWriter.write(
|
||||
page,
|
||||
buildSpec.getOutputDir().get(() ->
|
||||
new SsgException("outputDir Provider in " + buildSpec.getName() + " must be set.")
|
||||
),
|
||||
renderResult.getRight()
|
||||
);
|
||||
}, this.executorService))
|
||||
.toList();
|
||||
|
||||
CompletableFuture.allOf(renderAndWriteFutures.toArray(new CompletableFuture[0])).join();
|
||||
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package com.jessebrault.ssg;
|
||||
|
||||
import com.jessebrault.di.RegistryObjectFactory;
|
||||
import com.jessebrault.ssg.buildscript.BuildSpec;
|
||||
|
||||
public interface ObjectFactoryConfigurator {
|
||||
void configure(RegistryObjectFactory registryObjectFactory, BuildSpec buildSpec);
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
package com.jessebrault.ssg;
|
||||
|
||||
import com.jessebrault.di.ObjectFactory;
|
||||
import com.jessebrault.ssg.view.WvcPageView;
|
||||
import groowt.view.component.web.WebViewComponent;
|
||||
import groowt.view.component.web.WebViewComponentContext;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface PageContextFactory {
|
||||
WebViewComponentContext makeContext(
|
||||
WvcPageView wvcPageView,
|
||||
ObjectFactory buildObjectFactory,
|
||||
Set<Class<? extends WebViewComponent>> allWvcClasses
|
||||
);
|
||||
}
|
||||
18
api/src/main/groovy/com/jessebrault/ssg/PageRenderer.java
Normal file
18
api/src/main/groovy/com/jessebrault/ssg/PageRenderer.java
Normal file
@ -0,0 +1,18 @@
|
||||
package com.jessebrault.ssg;
|
||||
|
||||
import com.jessebrault.di.ObjectFactory;
|
||||
import com.jessebrault.fp.either.Either;
|
||||
import com.jessebrault.ssg.page.Page;
|
||||
import com.jessebrault.ssg.util.Diagnostic;
|
||||
import groowt.view.component.web.WebViewComponent;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface PageRenderer {
|
||||
Either<Diagnostic, String> renderPage(
|
||||
Page page,
|
||||
String baseUrl,
|
||||
ObjectFactory buildObjectFactory,
|
||||
Set<Class<? extends WebViewComponent>> allWvcClasses
|
||||
);
|
||||
}
|
||||
11
api/src/main/groovy/com/jessebrault/ssg/PageScanner.java
Normal file
11
api/src/main/groovy/com/jessebrault/ssg/PageScanner.java
Normal file
@ -0,0 +1,11 @@
|
||||
package com.jessebrault.ssg;
|
||||
|
||||
import com.jessebrault.di.ObjectFactory;
|
||||
import com.jessebrault.ssg.page.Page;
|
||||
import io.github.classgraph.ScanResult;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface PageScanner {
|
||||
Set<Page> getAllPages(ScanResult scanResult, ObjectFactory buildObjectFactory);
|
||||
}
|
||||
9
api/src/main/groovy/com/jessebrault/ssg/PageWriter.java
Normal file
9
api/src/main/groovy/com/jessebrault/ssg/PageWriter.java
Normal file
@ -0,0 +1,9 @@
|
||||
package com.jessebrault.ssg;
|
||||
|
||||
import com.jessebrault.ssg.page.Page;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public interface PageWriter {
|
||||
void write(Page page, File outputDir, String renderedPage);
|
||||
}
|
||||
@ -1,12 +1,13 @@
|
||||
package com.jessebrault.ssg
|
||||
package com.jessebrault.ssg;
|
||||
|
||||
import com.jessebrault.ssg.util.Diagnostic
|
||||
import com.jessebrault.ssg.util.Diagnostic;
|
||||
|
||||
interface StaticSiteGenerator {
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
public interface StaticSiteGenerator {
|
||||
Collection<Diagnostic> doBuild(
|
||||
File projectDir,
|
||||
String buildName,
|
||||
String buildScriptFqn,
|
||||
Map<String, String> buildScriptCliArgs
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
10
api/src/main/groovy/com/jessebrault/ssg/TextsGetter.java
Normal file
10
api/src/main/groovy/com/jessebrault/ssg/TextsGetter.java
Normal file
@ -0,0 +1,10 @@
|
||||
package com.jessebrault.ssg;
|
||||
|
||||
import com.jessebrault.ssg.buildscript.BuildSpec;
|
||||
import com.jessebrault.ssg.text.Text;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
public interface TextsGetter {
|
||||
Set<Text> getTexts(BuildSpec buildSpec);
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package com.jessebrault.ssg;
|
||||
|
||||
import com.jessebrault.ssg.view.WvcCompiler;
|
||||
|
||||
public interface WvcCompilerFactory {
|
||||
WvcCompiler getWvcCompiler();
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package com.jessebrault.ssg;
|
||||
|
||||
import com.jessebrault.ssg.view.WvcPageView;
|
||||
import groowt.view.component.web.WebViewComponentContext;
|
||||
|
||||
public interface WvcContextFactory {
|
||||
WebViewComponentContext getContext(WvcPageView currentPage);
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
package com.jessebrault.ssg.buildscript
|
||||
|
||||
import com.jessebrault.di.ObjectFactory
|
||||
import com.jessebrault.ssg.buildscript.delegates.BuildDelegate
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.Nullable
|
||||
@ -25,6 +26,8 @@ abstract class BuildScriptBase extends Script {
|
||||
private Closure buildClosure = { }
|
||||
private File projectRoot
|
||||
private String buildName
|
||||
private ObjectFactory objectFactory
|
||||
private Map<String, String> cliArgs
|
||||
|
||||
/* --- Instance DSL helpers --- */
|
||||
|
||||
@ -73,4 +76,21 @@ abstract class BuildScriptBase extends Script {
|
||||
this.buildClosure
|
||||
}
|
||||
|
||||
ObjectFactory getObjectFactory() {
|
||||
return objectFactory
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
void setObjectFactory(ObjectFactory objectFactory) {
|
||||
this.objectFactory = objectFactory
|
||||
}
|
||||
|
||||
Map<String, String> getCliArgs() {
|
||||
return cliArgs
|
||||
}
|
||||
|
||||
void setCliArgs(Map<String, String> cliArgs) {
|
||||
this.cliArgs = cliArgs
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
package com.jessebrault.ssg.buildscript;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface BuildScriptFactory {
|
||||
BuildScriptBase getAndRunBuildScript(String scriptFqn, Map<String, String> scriptCliArgs);
|
||||
}
|
||||
@ -5,13 +5,12 @@ 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 BuildScriptFactory buildScriptFactory
|
||||
private final Function<String, BuildDelegate> buildDelegateFactory
|
||||
|
||||
protected BuildSpec getFromDelegate(String name, BuildDelegate delegate) {
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
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 com.jessebrault.ssg.model.Model
|
||||
import com.jessebrault.ssg.text.TextSupplier
|
||||
import groovy.transform.EqualsAndHashCode
|
||||
import groovy.transform.NullCheck
|
||||
|
||||
import static com.jessebrault.ssg.util.ObjectUtil.requireProvider
|
||||
import static com.jessebrault.ssg.util.ObjectUtil.requireString
|
||||
@ -21,9 +21,8 @@ final class BuildSpec {
|
||||
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
|
||||
final Provider<Set<TextSupplier>> textSuppliers
|
||||
final Provider<RegistryObjectFactory> objectFactory
|
||||
|
||||
@SuppressWarnings('GroovyAssignabilityCheck')
|
||||
BuildSpec(Map args) {
|
||||
@ -34,15 +33,14 @@ final class BuildSpec {
|
||||
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)
|
||||
this.textSuppliers = requireProvider(args.textSuppliers)
|
||||
this.objectFactory = requireProvider(args.objectFactory)
|
||||
}
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
"Build(name: ${this.name}, basePackages: $basePackages, siteName: $siteName, " +
|
||||
"baseUrl: $baseUrl, outputDir: $outputDir, textsDirs: $textsDirs)"
|
||||
"baseUrl: $baseUrl, outputDir: $outputDir, textSuppliers: $textSuppliers)"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
package com.jessebrault.ssg.buildscript;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface BuildSpecFactory {
|
||||
BuildSpec getBuildSpec(String scriptFqn, Map<String, String> scriptCliArgs);
|
||||
}
|
||||
@ -0,0 +1,78 @@
|
||||
package com.jessebrault.ssg.buildscript;
|
||||
|
||||
import com.jessebrault.di.ObjectFactory;
|
||||
import groovy.lang.GroovyClassLoader;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import org.codehaus.groovy.control.CompilerConfiguration;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class DefaultBuildScriptFactory implements BuildScriptFactory {
|
||||
|
||||
private final GroovyClassLoader groovyClassLoader;
|
||||
private final List<URL> scriptBaseUrls;
|
||||
private final File projectDir;
|
||||
private final ObjectFactory objectFactory;
|
||||
|
||||
@Inject
|
||||
public DefaultBuildScriptFactory(
|
||||
GroovyClassLoader groovyClassLoader,
|
||||
@Named("scriptBaseUrls") List<URL> scriptBaseUrls,
|
||||
@Named("projectDir") File projectDir,
|
||||
ObjectFactory objectFactory
|
||||
) {
|
||||
this.groovyClassLoader = groovyClassLoader;
|
||||
this.scriptBaseUrls = scriptBaseUrls;
|
||||
this.projectDir = projectDir;
|
||||
this.objectFactory = objectFactory;
|
||||
}
|
||||
|
||||
protected GroovyClassLoader getScriptClassLoader() {
|
||||
// set up gcl with our base script class
|
||||
final var compilerConfiguration = new CompilerConfiguration();
|
||||
compilerConfiguration.setScriptBaseClass(BuildScriptBase.class.getName());
|
||||
final var scriptGroovyClassLoader = new GroovyClassLoader(
|
||||
this.groovyClassLoader,
|
||||
compilerConfiguration
|
||||
);
|
||||
|
||||
// add urls where to find scripts
|
||||
for (final var url : this.scriptBaseUrls) {
|
||||
scriptGroovyClassLoader.addURL(url);
|
||||
}
|
||||
|
||||
return scriptGroovyClassLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BuildScriptBase getAndRunBuildScript(String scriptFqn, Map<String, String> scriptCliArgs) {
|
||||
try (final GroovyClassLoader scriptClassLoader = this.getScriptClassLoader()) {
|
||||
// Get script instance
|
||||
@SuppressWarnings("unchecked")
|
||||
final Class<? extends BuildScriptBase> scriptClass = (Class<? extends BuildScriptBase>)
|
||||
scriptClassLoader.loadClass(scriptFqn, true, true);
|
||||
final BuildScriptBase script = scriptClass.getConstructor().newInstance();
|
||||
|
||||
// configure props
|
||||
script.setProjectRoot(this.projectDir);
|
||||
script.setBuildName(scriptFqn);
|
||||
script.setObjectFactory(this.objectFactory);
|
||||
script.setCliArgs(scriptCliArgs);
|
||||
|
||||
// run
|
||||
script.run();
|
||||
|
||||
return script;
|
||||
} catch (IOException | ClassNotFoundException | NoSuchMethodException | InstantiationException |
|
||||
IllegalAccessException | InvocationTargetException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
package com.jessebrault.ssg.buildscript;
|
||||
|
||||
import com.jessebrault.ssg.buildscript.delegates.BuildDelegate;
|
||||
import com.jessebrault.ssg.buildscript.delegates.BuildDelegateConfigurator;
|
||||
import com.jessebrault.ssg.buildscript.delegates.BuildDelegateConverter;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Deque;
|
||||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
|
||||
public class DefaultBuildSpecFactory implements BuildSpecFactory {
|
||||
|
||||
private final BuildScriptFactory buildScriptFactory;
|
||||
private final BuildDelegateConfigurator buildDelegateConfigurator;
|
||||
private final BuildDelegateConverter buildDelegateConverter;
|
||||
private final File projectDir;
|
||||
|
||||
@Inject
|
||||
public DefaultBuildSpecFactory(
|
||||
BuildScriptFactory buildScriptFactory,
|
||||
BuildDelegateConfigurator buildDelegateConfigurator,
|
||||
BuildDelegateConverter buildDelegateConverter,
|
||||
@Named("projectDir") File projectDir
|
||||
) {
|
||||
this.buildScriptFactory = buildScriptFactory;
|
||||
this.buildDelegateConfigurator = buildDelegateConfigurator;
|
||||
this.buildDelegateConverter = buildDelegateConverter;
|
||||
this.projectDir = projectDir;
|
||||
}
|
||||
|
||||
protected BuildSpec doConvert(String scriptFqn, Map<String, String> scriptCliArgs, BuildScriptBase script) {
|
||||
// 1. Make hierarchy as a stack
|
||||
final Deque<BuildScriptBase> buildHierarchy = new LinkedList<>();
|
||||
buildHierarchy.push(script);
|
||||
@Nullable String extending = script.getExtending();
|
||||
while (extending != null) {
|
||||
final BuildScriptBase from = this.buildScriptFactory.getAndRunBuildScript(extending, scriptCliArgs);
|
||||
buildHierarchy.push(from);
|
||||
extending = from.getExtending();
|
||||
}
|
||||
|
||||
// Go through the stack from top to bottom, using the same delegate
|
||||
final BuildDelegate buildDelegate = new BuildDelegate(this.projectDir);
|
||||
this.buildDelegateConfigurator.configure(buildDelegate, scriptFqn);
|
||||
while (!buildHierarchy.isEmpty()) {
|
||||
final BuildScriptBase from = buildHierarchy.pop();
|
||||
from.getBuildClosure().setDelegate(buildDelegate);
|
||||
from.getBuildClosure().run();
|
||||
}
|
||||
|
||||
return this.buildDelegateConverter.convert(scriptFqn, buildDelegate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BuildSpec getBuildSpec(String scriptFqn, Map<String, String> scriptCliArgs) {
|
||||
final BuildScriptBase script = this.buildScriptFactory.getAndRunBuildScript(scriptFqn, scriptCliArgs);
|
||||
return this.doConvert(scriptFqn, scriptCliArgs, script);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,35 +1,18 @@
|
||||
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 com.jessebrault.ssg.model.Model
|
||||
import com.jessebrault.ssg.model.Models
|
||||
import com.jessebrault.ssg.text.TextSupplier
|
||||
|
||||
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)
|
||||
@ -38,12 +21,10 @@ final class BuildDelegate {
|
||||
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)
|
||||
final Property<Set<TextSupplier>> textSuppliers = DefaultProperty.<Set<TextSupplier>>empty(Set)
|
||||
final Property<RegistryObjectFactory> objectFactory = DefaultProperty.empty(RegistryObjectFactory)
|
||||
|
||||
private BuildDelegate(File projectDir) {
|
||||
BuildDelegate(File projectDir) {
|
||||
this.projectDir = projectDir
|
||||
}
|
||||
|
||||
@ -109,24 +90,12 @@ final class BuildDelegate {
|
||||
this.models.configure { it.add(Models.ofNamedProvider(namedProvider)) }
|
||||
}
|
||||
|
||||
void textsDir(File textsDir) {
|
||||
this.textsDirs.configure { it.add(textsDir) }
|
||||
void textSupplier(TextSupplier toAdd) {
|
||||
this.textSuppliers.configure { it.add(toAdd) }
|
||||
}
|
||||
|
||||
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)
|
||||
void textSuppliers(TextSupplier... toAdd) {
|
||||
this.textSuppliers.configure { it.addAll(toAdd) }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
package com.jessebrault.ssg.buildscript.delegates;
|
||||
|
||||
public interface BuildDelegateConfigurator {
|
||||
void configure(BuildDelegate buildDelegate, String buildName);
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package com.jessebrault.ssg.buildscript.delegates;
|
||||
|
||||
import com.jessebrault.ssg.buildscript.BuildSpec;
|
||||
|
||||
public interface BuildDelegateConverter {
|
||||
BuildSpec convert(String buildName, BuildDelegate buildDelegate);
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
package com.jessebrault.ssg.buildscript.delegates
|
||||
|
||||
import com.jessebrault.di.DefaultRegistryObjectFactory
|
||||
import com.jessebrault.ssg.model.Model
|
||||
import com.jessebrault.ssg.text.TextSupplier
|
||||
import com.jessebrault.ssg.text.TextsDirMarkdownTextSupplier
|
||||
import com.jessebrault.ssg.util.PathUtil
|
||||
import jakarta.inject.Inject
|
||||
import jakarta.inject.Named
|
||||
|
||||
import java.nio.file.Path
|
||||
|
||||
class DefaultBuildDelegateConfigurator implements BuildDelegateConfigurator {
|
||||
|
||||
private final File projectDir
|
||||
private final TextsDirMarkdownTextSupplier textsDirMarkdownTextSupplier
|
||||
|
||||
@Inject
|
||||
DefaultBuildDelegateConfigurator(
|
||||
@Named('projectDir') File projectDir,
|
||||
TextsDirMarkdownTextSupplier textsDirMarkdownTextSupplier
|
||||
) {
|
||||
this.projectDir = projectDir
|
||||
this.textsDirMarkdownTextSupplier = textsDirMarkdownTextSupplier
|
||||
}
|
||||
|
||||
@Override
|
||||
void configure(BuildDelegate buildDelegate, String buildName) {
|
||||
buildDelegate.tap {
|
||||
basePackages.convention = [] as Set<String>
|
||||
outputDir.convention = PathUtil.resolve(this.projectDir, Path.of('dist', buildName.split(/\\./)))
|
||||
globals.convention = [:]
|
||||
models.convention = [] as Set<Model>
|
||||
textSuppliers.convention = [this.textsDirMarkdownTextSupplier] as Set<TextSupplier>
|
||||
objectFactory.convention = DefaultRegistryObjectFactory.Builder.withDefaults().build()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package com.jessebrault.ssg.buildscript.delegates
|
||||
|
||||
import com.jessebrault.ssg.buildscript.BuildSpec
|
||||
|
||||
class DefaultBuildDelegateConverter implements BuildDelegateConverter {
|
||||
|
||||
@Override
|
||||
BuildSpec convert(String buildName, BuildDelegate delegate) {
|
||||
return new BuildSpec(
|
||||
name: buildName,
|
||||
basePackages: delegate.basePackages,
|
||||
siteName: delegate.siteName,
|
||||
baseUrl: delegate.baseUrl,
|
||||
outputDir: delegate.outputDir,
|
||||
globals: delegate.globals,
|
||||
models: delegate.models,
|
||||
textSuppliers: delegate.textSuppliers,
|
||||
objectFactory: delegate.objectFactory
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package com.jessebrault.ssg.text;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface TextSupplier {
|
||||
Collection<Text> get();
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
package com.jessebrault.ssg.text;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class TextsDirMarkdownTextSupplier implements TextSupplier {
|
||||
|
||||
private final File projectDir;
|
||||
private final ExecutorService executorService;
|
||||
private final MarkdownTextConverter markdownTextConverter;
|
||||
|
||||
@Inject
|
||||
public TextsDirMarkdownTextSupplier(
|
||||
@Named("projectDir") File projectDir,
|
||||
ExecutorService executorService,
|
||||
MarkdownTextConverter markdownTextConverter
|
||||
) {
|
||||
this.projectDir = projectDir;
|
||||
this.executorService = executorService;
|
||||
this.markdownTextConverter = markdownTextConverter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Text> get() {
|
||||
final Path textsDir = Paths.get(projectDir.getAbsolutePath(), "texts");
|
||||
final Collection<Text> results = ConcurrentHashMap.newKeySet();
|
||||
if (Files.exists(textsDir)) {
|
||||
try (final Stream<Path> walkStream = Files.walk(textsDir)){
|
||||
final List<CompletableFuture<Void>> textFutures = walkStream
|
||||
.map(path -> {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
if (path.endsWith(".md")) {
|
||||
results.add(this.markdownTextConverter.convert(textsDir.toFile(), path.toFile()));
|
||||
}
|
||||
}, this.executorService);
|
||||
})
|
||||
.toList();
|
||||
CompletableFuture.allOf(textFutures.toArray(new CompletableFuture[0])).join();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
}
|
||||
@ -4,13 +4,17 @@ 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
|
||||
|
||||
Diagnostic(String message, Exception exception) {
|
||||
this.message = message
|
||||
this.exception = exception
|
||||
}
|
||||
|
||||
@Override
|
||||
String toString() {
|
||||
if (this.exception != null) {
|
||||
|
||||
@ -1,15 +1,13 @@
|
||||
package com.jessebrault.ssg.view
|
||||
|
||||
import com.jessebrault.ssg.util.Diagnostic
|
||||
import groovy.transform.TupleConstructor
|
||||
import com.jessebrault.fp.either.Either
|
||||
import com.jessebrault.ssg.util.Diagnostic
|
||||
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 {
|
||||
@ -30,6 +28,11 @@ class WvcCompiler {
|
||||
final GroovyClassLoader groovyClassLoader
|
||||
final ComponentTemplateClassFactory templateClassFactory
|
||||
|
||||
WvcCompiler(GroovyClassLoader groovyClassLoader, ComponentTemplateClassFactory templateClassFactory) {
|
||||
this.groovyClassLoader = groovyClassLoader
|
||||
this.templateClassFactory = templateClassFactory
|
||||
}
|
||||
|
||||
Either<Diagnostic, ComponentTemplate> compileTemplate(
|
||||
Class<? extends ViewComponent> componentClass,
|
||||
String resourceName
|
||||
@ -37,7 +40,8 @@ class WvcCompiler {
|
||||
def templateUrl = componentClass.getResource(resourceName)
|
||||
if (templateUrl == null) {
|
||||
return Either.left(new Diagnostic(
|
||||
"Could not find templateResource: $resourceName"
|
||||
"Could not find templateResource: $resourceName",
|
||||
null
|
||||
))
|
||||
}
|
||||
def source = ComponentTemplateSource.of(templateUrl)
|
||||
|
||||
@ -4,7 +4,7 @@ plugins {
|
||||
}
|
||||
|
||||
group 'com.jessebrault.ssg'
|
||||
version '0.6.3'
|
||||
version '0.7.0-SNAPSHOT'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
@ -1,8 +1,20 @@
|
||||
package com.jessebrault.ssg
|
||||
|
||||
import com.jessebrault.di.DefaultRegistryObjectFactory
|
||||
import com.jessebrault.di.ObjectFactory
|
||||
import com.jessebrault.di.RegistryObjectFactory
|
||||
import com.jessebrault.ssg.buildscript.BuildScriptFactory
|
||||
import com.jessebrault.ssg.buildscript.BuildSpecFactory
|
||||
import com.jessebrault.ssg.buildscript.DefaultBuildScriptFactory
|
||||
import com.jessebrault.ssg.buildscript.DefaultBuildSpecFactory
|
||||
import com.jessebrault.ssg.buildscript.delegates.BuildDelegateConfigurator
|
||||
import com.jessebrault.ssg.buildscript.delegates.BuildDelegateConverter
|
||||
import com.jessebrault.ssg.buildscript.delegates.DefaultBuildDelegateConfigurator
|
||||
import com.jessebrault.ssg.buildscript.delegates.DefaultBuildDelegateConverter
|
||||
import com.jessebrault.ssg.gradle.SsgBuildModel
|
||||
import com.jessebrault.ssg.util.Diagnostic
|
||||
import com.jessebrault.ssg.util.URLUtil
|
||||
import com.jessebrault.ssg.view.WvcCompiler
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import org.apache.logging.log4j.Logger
|
||||
import org.gradle.tooling.GradleConnector
|
||||
@ -10,6 +22,10 @@ import picocli.CommandLine
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
import static com.jessebrault.di.BindingUtil.*
|
||||
|
||||
abstract class AbstractBuildCommand extends AbstractSubCommand {
|
||||
|
||||
@ -74,8 +90,47 @@ abstract class AbstractBuildCommand extends AbstractSubCommand {
|
||||
)
|
||||
boolean profile
|
||||
|
||||
@CommandLine.Option(
|
||||
names = ['-t', '--threads'],
|
||||
description = 'The number of threads to use.',
|
||||
defaultValue = '8'
|
||||
)
|
||||
Integer threads
|
||||
|
||||
protected RegistryObjectFactory objectFactory = null
|
||||
protected StaticSiteGenerator staticSiteGenerator = null
|
||||
|
||||
protected RegistryObjectFactory getApiObjectFactory(
|
||||
GroovyClassLoader groovyClassLoader,
|
||||
ExecutorService executorService,
|
||||
PageWriter pageWriter,
|
||||
List<URL> scriptBaseUrls,
|
||||
File projectDir
|
||||
) {
|
||||
RegistryObjectFactory objectFactory = DefaultRegistryObjectFactory.Builder.withDefaults().build()
|
||||
objectFactory.tap {
|
||||
configureRegistry {
|
||||
bind(BuildSpecFactory, toClass(DefaultBuildSpecFactory))
|
||||
bind(ObjectFactoryConfigurator, toClass(DefaultObjectFactoryConfigurator))
|
||||
bind(GroovyClassLoader, toSingleton(groovyClassLoader))
|
||||
bind(ExecutorService, toSingleton(executorService))
|
||||
bind(PageScanner, toClass(DefaultPageScanner))
|
||||
bind(ComponentClassScanner, toClass(DefaultComponentClassScanner))
|
||||
bind(PageRenderer, toClass(DefaultPageRenderer))
|
||||
bind(PageWriter, toSingleton(pageWriter))
|
||||
bind(BuildScriptFactory, toClass(DefaultBuildScriptFactory))
|
||||
bind(BuildDelegateConfigurator, toClass(DefaultBuildDelegateConfigurator))
|
||||
bind(BuildDelegateConverter, toClass(DefaultBuildDelegateConverter))
|
||||
bind(named('scriptBaseUrls', List), toSingleton(scriptBaseUrls))
|
||||
bind(named('projectDir', File), toSingleton(projectDir))
|
||||
bind(TextsGetter, toClass(DefaultTextsGetter))
|
||||
bind(WvcCompiler, toSelf())
|
||||
bind(PageContextFactory, toClass(DefaultPageContextFactory))
|
||||
bind(ObjectFactory, toSingleton(objectFactory))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected final Integer doSingleBuild(String buildName) {
|
||||
logger.traceEntry('buildName: {}', buildName)
|
||||
|
||||
@ -126,23 +181,21 @@ abstract class AbstractBuildCommand extends AbstractSubCommand {
|
||||
def buildScriptDirUrls = this.buildScriptDirs.collect {
|
||||
def withProjectDir = new File(this.commonCliOptions.projectDir, it.toString())
|
||||
withProjectDir.toURI().toURL()
|
||||
} as URL[]
|
||||
}
|
||||
|
||||
this.staticSiteGenerator = new DefaultStaticSiteGenerator(
|
||||
this.objectFactory = this.getApiObjectFactory(
|
||||
groovyClassLoader,
|
||||
Executors.newFixedThreadPool(this.threads),
|
||||
!this.dryRun ? new FilePageWriter() : new ConsolePageWriter(),
|
||||
buildScriptDirUrls,
|
||||
this.dryRun
|
||||
new File('.')
|
||||
)
|
||||
this.staticSiteGenerator = objectFactory.createInstance(JDefaultStaticSiteGenerator)
|
||||
}
|
||||
|
||||
def buildStartTime = System.currentTimeMillis()
|
||||
|
||||
final Collection<Diagnostic> diagnostics = this.staticSiteGenerator.doBuild(
|
||||
this.commonCliOptions.projectDir,
|
||||
buildName,
|
||||
buildName,
|
||||
this.scriptArgs ?: [:]
|
||||
)
|
||||
final Collection<Diagnostic> diagnostics = this.staticSiteGenerator.doBuild(buildName, this.scriptArgs ?: [:])
|
||||
|
||||
def buildElapsedTime = System.currentTimeMillis() - buildStartTime
|
||||
if (this.profile) {
|
||||
|
||||
@ -5,7 +5,7 @@ import picocli.CommandLine
|
||||
@CommandLine.Command(
|
||||
name = 'ssg',
|
||||
mixinStandardHelpOptions = true,
|
||||
version = '0.6.3',
|
||||
version = '0.7.0-SNAPSHOT',
|
||||
description = 'A static site generator which can interface with Gradle for high extensibility.',
|
||||
subcommands = [SsgInit, SsgBuild, SsgWatch]
|
||||
)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
[versions]
|
||||
classgraph = '4.8.179'
|
||||
classgraph = '4.8.184'
|
||||
commonmark = '0.24.0'
|
||||
di = '0.1.0'
|
||||
fp = '0.1.0'
|
||||
|
||||
@ -162,8 +162,8 @@ public class SsgGradlePlugin implements Plugin<Project> {
|
||||
Configuration ssgApiConfiguration,
|
||||
Configuration ssgCliConfiguration
|
||||
) {
|
||||
final Dependency ssgApi = project.getDependencies().create("com.jessebrault.ssg:api:0.6.3");
|
||||
final Dependency ssgCli = project.getDependencies().create("com.jessebrault.ssg:cli:0.6.3");
|
||||
final Dependency ssgApi = project.getDependencies().create("com.jessebrault.ssg:api:0.7.0-SNAPSHOT");
|
||||
final Dependency ssgCli = project.getDependencies().create("com.jessebrault.ssg:cli:0.7.0-SNAPSHOT");
|
||||
ssgApiConfiguration.getDependencies().add(ssgApi);
|
||||
ssgCliConfiguration.getDependencies().add(ssgCli);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user