Compare commits

..

No commits in common. "main" and "v0.1.1" have entirely different histories.
main ... v0.1.1

84 changed files with 1316 additions and 513 deletions

View File

@ -1,36 +0,0 @@
name: Groowt Check and Publish
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 publishViewsPublicationToGiteaRepository &&
./gradlew publishViewComponentsPublicationToGiteaRepository &&
./gradlew publishWebViewComponentsPublicationToGiteaRepository &&
./gradlew publishWebViewComponentsCompilerPublicationToGiteaRepository &&
./gradlew publishDiPublicationToGiteaRepository &&
./gradlew publishExtensiblePublicationToGiteaRepository &&
./gradlew publishFpPublicationToGiteaRepository
- name: Publish to archiva.jessebrault.com
run: >
./gradlew publishViewsPublicationToJbArchivaInternalRepository &&
./gradlew publishViewComponentsPublicationToJbArchivaInternalRepository &&
./gradlew publishWebViewComponentsPublicationToJbArchivaInternalRepository &&
./gradlew publishWebViewComponentsCompilerPublicationToJbArchivaInternalRepository &&
./gradlew publishDiPublicationToJbArchivaInternalRepository &&
./gradlew publishExtensiblePublicationToJbArchivaInternalRepository &&
./gradlew publishFpPublicationToJbArchivaInternalRepository

36
TODO.md
View File

@ -1,17 +1,5 @@
# TODO
## 0.3.0
- [ ] Explore slightly different syntax for web view components to allow better InteliJ and Groovy integration.
For example:
```
@package mysite
@import mysite.Component
<html>
<Component componentAttrWithParams={ key, value -> <Echo>$key: $value</Echo>} />
</html>
```
## 0.2.0
- [ ] Separate out the following into separate, non-Groowt projects with their own repositories and the com.jessebrault
namespace:
@ -20,31 +8,9 @@ For example:
- di
- extensible
- fp
- [x] Remove cli, groowt-all, groowt-gradle, groowt-gradle-model.
- [ ] Get rid of wvc compiler dependency on fp, di.
- [ ] Remove as much cruft as possible from web-view-components-compiler, etc.
- [ ] Use new namespaces (i.e., packages) the individual projects:
- `com.jessebrault.groowt`: for views
- `com.jessebrault.groowt.component`: for view-components
- `com.jessebrault.groowt.web`: for web-view-components and web-view-components-compiler
## 0.1.3
- [ ] ~~refactor tools/gradle start scripts to use dist instead of custom bin script~~
- [ ] ~~have custom bin/* scripts which point to dist(s) for convenience~~
- [x] di bug: @Singleton toSelf() causes stack overflow
- [x] wvcc bug: Nested static view classes are not seen by compiler
- This required tweaking how the configurations are passed around. Ultimately, we should strive for less complexity
in this regard.
- [x] `OutletContainer` trait or interface for components which can contain an `<Outlet />` child.
- [x] `Context` should have methods for simply finding an ancestor of a certain type without the need for a predicate.
## 0.1.2
- [x] `Outlet` component for rendering children like so:
```
<Outlet children={children} />
```
- [x] `Render` component
- [x] `data-` attributes need to function correctly (really any attribute with hyphen).
- [ ] di bug: @Singleton toSelf() causes stack overflow
## 0.1.1
- [x] `Switch` and `Case` components

View File

@ -4,7 +4,7 @@ plugins {
}
group = 'groowt'
version = '0.1.3'
version = '0.1.1'
repositories {
mavenCentral()

View File

@ -4,8 +4,6 @@ import com.jessebrault.jbarchiva.JbArchivaPlugin
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin
import org.gradle.api.credentials.HttpHeaderCredentials
import org.gradle.authentication.http.HttpHeaderAuthentication
class GroowtPublishPlugin implements Plugin<Project> {
@ -13,25 +11,6 @@ class GroowtPublishPlugin implements Plugin<Project> {
void apply(Project project) {
project.plugins.apply(MavenPublishPlugin)
project.plugins.apply(JbArchivaPlugin)
project.with {
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)
}
}
}
}
}
}
}

46
cli/build.gradle Normal file
View File

@ -0,0 +1,46 @@
plugins {
id 'groowt-conventions'
id 'groowt-logging'
id 'groowt-publish'
}
repositories {
maven {
url 'https://repo.gradle.org/gradle/libs-releases'
}
}
dependencies {
implementation libs.gradle.tooling
implementation libs.picocli
implementation project(':groowt-gradle-model')
}
tasks.named('jar', Jar) {
manifest {
attributes('Main-Class': 'groowt.cli.GroowtCli')
}
from sourceSets.main.runtimeClasspath.filter(File.&exists).collect { it.isDirectory() ? it : zipTree(it) }
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
dependsOn ':groowt-gradle-model:jar'
}
tasks.withType(GenerateModuleMetadata).configureEach {
enabled = false
}
publishing {
publications {
create('groowtCli', MavenPublication) {
artifactId = 'groowt-cli'
from components.java
pom {
withXml {
def rootNode = asNode()
def dependenciesNode = rootNode.get('dependencies')
rootNode.remove(dependenciesNode)
}
}
}
}
}

View File

@ -0,0 +1,17 @@
package groowt.cli;
import java.io.File;
public final class FileAndPathUtil {
public static File packageNameToFile(String packageName) {
return new File(packageName.replace(".", File.separator));
}
public static File resolve(File from, File to) {
return from.toPath().resolve(to.toPath()).toFile();
}
private FileAndPathUtil() {}
}

View File

@ -0,0 +1,61 @@
package groowt.cli;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.Callable;
@Command(name = "generate", aliases = "gen", description = "Generate a component, template, model, etc.")
public final class Generate implements Callable<Integer> {
@CommandLine.ParentCommand
private GroowtCli cli;
@CommandLine.Option(
names = { "-c", "--component" },
description = "Create a component with the given name."
)
private String componentName;
@CommandLine.Option(
names = { "-s", "--sourceSet" },
description = "The source set in which to generate the component, etc.",
defaultValue = "main"
)
private String sourceSet;
@CommandLine.Option(
names = { "-d", "--srcDir", "--sourceDir", "--sourceDirectory" },
description = "The directory in the source set in which to generate the component, etc."
)
private File sourceDir;
@SuppressWarnings("ResultOfMethodCallIgnored")
@Override
public Integer call() {
if (this.componentName != null) {
GradleUtil.doWithGroowtGradleModel(this.cli.getProjectDir(), model -> {
if (this.sourceDir == null) {
this.sourceDir = new File(String.join(File.separator, "src", this.sourceSet, "groovy"));
}
final File packageDir = FileAndPathUtil.resolve(
this.sourceDir,
FileAndPathUtil.packageNameToFile(model.getBasePackage())
);
packageDir.mkdirs();
final File componentFile = new File(packageDir, this.componentName + ".txt");
try (final OutputStream componentFileOutputStream = new FileOutputStream(componentFile)) {
componentFileOutputStream.write("Hello, Groowt!".getBytes());
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
return 0;
}
}

View File

@ -0,0 +1,32 @@
package groowt.cli;
import groowt.gradle.model.GroowtGradleModel;
import org.gradle.tooling.GradleConnector;
import org.gradle.tooling.ProjectConnection;
import java.io.File;
import java.util.function.Consumer;
public final class GradleUtil {
public static void doWith(File projectDir, Consumer<? super ProjectConnection> action) {
final var gradleConnector = GradleConnector.newConnector().forProjectDirectory(projectDir);
try (final var projectConnection = gradleConnector.connect()) {
action.accept(projectConnection);
}
}
public static <T> void doWith(File projectDir, Class<? extends T> modelClass, Consumer<? super T> modelConsumer) {
doWith(projectDir, projectConnection -> {
final T model = projectConnection.getModel(modelClass);
modelConsumer.accept(model);
});
}
public static void doWithGroowtGradleModel(File projectDir, Consumer<? super GroowtGradleModel> modelConsumer) {
doWith(projectDir, GroowtGradleModel.class, modelConsumer);
}
private GradleUtil() {}
}

View File

@ -0,0 +1,46 @@
package groowt.cli;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import picocli.CommandLine;
import java.io.File;
@CommandLine.Command(
name = "groowt",
description = "The command line interface facilitating development of a Groowt project.",
mixinStandardHelpOptions = true,
version = "0.1.0",
subcommands = { Generate.class }
)
public final class GroowtCli {
private static final Logger logger = LoggerFactory.getLogger(GroowtCli.class);
@CommandLine.Option(
names = { "-v", "--verbose" },
description = "Log verbosely to standard out."
)
private boolean verbose;
@CommandLine.Option(
names = { "--projectDir" },
defaultValue = ".",
description = "The root directory of the groowt project."
)
private File projectDir;
public static void main(String[] args) {
logger.info("Hello from Groowt! Version 0.1.0");
System.exit(new CommandLine(new GroowtCli()).execute(args));
}
public boolean isVerbose() {
return this.verbose;
}
public File getProjectDir() {
return this.projectDir;
}
}

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Configuration xmlns="http://logging.apache.org/log4j/2.0/config">
<Appenders>
<Console name="root">
<PatternLayout>
<LevelPatternSelector defaultPattern="[%t] %-5level %logger{1.} %msg%n">
<PatternMatch key="DEBUG" pattern="[%t] %-5level %logger{1.}.%M() %msg%n"/>
</LevelPatternSelector>
</PatternLayout>
</Console>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="root"/>
</Root>
</Loggers>
</Configuration>

View File

@ -2,31 +2,34 @@
# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format
[versions]
antlr = '4.13.2'
groovy = '4.0.25'
junit = '5.11.4'
kotlin = '1.9.25'
log4j = '2.24.3'
mockito = '5.15.2'
slf4j = '2.0.16'
antlr = '4.13.1'
groovy = '4.0.21'
junit = '5.10.2'
kotlin = '1.9.23'
log4j = '2.23.1'
mockito = '5.11.0'
slf4j = '2.0.12'
[libraries]
antlr = { module = 'org.antlr:antlr4', version.ref = 'antlr' }
antlr-runtime = { module = 'org.antlr:antlr4-runtime', version.ref = 'antlr' }
asm = 'org.ow2.asm:asm:9.7.1'
gradle-tooling = 'org.gradle:gradle-tooling-api:8.12.1'
asm = 'org.ow2.asm:asm:9.7'
classgraph = 'io.github.classgraph:classgraph:4.8.172'
gradle-tooling = 'org.gradle:gradle-tooling-api:8.6'
groovy = { module = 'org.apache.groovy:groovy', version.ref = 'groovy' }
groovy-all = { module = 'org.apache.groovy:groovy-all', version.ref = 'groovy' }
groovy-console = { module = 'org.apache.groovy:groovy-console', version.ref = 'groovy' }
groovy-templates = { module = 'org.apache.groovy:groovy-templates', version.ref = 'groovy' }
jakarta-inject = 'jakarta.inject:jakarta.inject-api:2.0.1'
jansi = 'org.fusesource.jansi:jansi:2.4.1'
jbarchiva = 'com.jessebrault.jbarchiva:jbarchiva:0.2.2'
jetbrains-anotations = 'org.jetbrains:annotations:26.0.2'
jbarchiva = 'com.jessebrault.jbarchiva:jbarchiva:0.2.1'
jetbrains-anotations = 'org.jetbrains:annotations:24.1.0'
junit-jupiter-api = { module = 'org.junit.jupiter:junit-jupiter-api', version.ref = 'junit' }
kotlin-stdlib = { module = 'org.jetbrains.kotlin:kotlin-stdlib', version.ref = 'kotlin' }
kotlin-test = { module = 'org.jetbrains.kotlin:kotlin-test', version.ref = 'kotlin' }
log4j-core = { module = 'org.apache.logging.log4j:log4j-core', version.ref = 'log4j' }
log4j-slf4jBinding = { module = 'org.apache.logging.log4j:log4j-slf4j2-impl', version.ref = 'log4j' }
mockito-core = { module = 'org.mockito:mockito-core', version.ref = 'mockito' }
mockito-junit = { module = 'org.mockito:mockito-junit-jupiter', version.ref = 'mockito' }
picocli = 'info.picocli:picocli:4.7.6'
picocli = 'info.picocli:picocli:4.7.5'
slf4j-api = { module = 'org.slf4j:slf4j-api', version.ref = 'slf4j' }

Binary file not shown.

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

6
gradlew vendored
View File

@ -15,8 +15,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@ -57,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@ -86,7 +84,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# 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
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum

2
gradlew.bat vendored
View File

@ -13,8 +13,6 @@
@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 ##########################################################################

23
groowt-all/build.gradle Normal file
View File

@ -0,0 +1,23 @@
plugins {
id 'groowt-conventions'
id 'groowt-publish'
id 'java-library'
}
dependencies {
api project(':views')
api project(':view-components')
api project(':web-view-components')
api project(':di')
api project(':extensible')
api project(':fp')
}
publishing {
publications {
create('groowtAll', MavenPublication) {
artifactId = 'groowt-all'
from components.java
}
}
}

View File

@ -0,0 +1,14 @@
plugins {
id 'groowt-conventions'
id 'java-library'
id 'groowt-publish'
}
publishing {
publications {
create('groowtGradleModel', MavenPublication) {
artifactId = 'groowt-gradle-model'
from components.java
}
}
}

View File

@ -0,0 +1,32 @@
package groowt.gradle.model;
import java.io.File;
import java.io.Serializable;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
public class DefaultGroowtGradleModel implements GroowtGradleModel, Serializable {
private String basePackage;
private Map<String, Set<File>> sourceSetToTemplatesDirs;
@Override
public String getBasePackage() {
return this.basePackage;
}
public void setBasePackage(String basePackage) {
this.basePackage = Objects.requireNonNull(basePackage);
}
@Override
public Map<String, Set<File>> getSourceSetToTemplatesDirs() {
return Objects.requireNonNull(this.sourceSetToTemplatesDirs);
}
public void setSourceFileSets(Map<String, Set<File>> sourceSetToTemplateDir) {
this.sourceSetToTemplatesDirs = sourceSetToTemplateDir;
}
}

View File

@ -0,0 +1,10 @@
package groowt.gradle.model;
import java.io.File;
import java.util.Map;
import java.util.Set;
public interface GroowtGradleModel {
String getBasePackage();
Map<String, Set<File>> getSourceSetToTemplatesDirs();
}

View File

@ -0,0 +1,32 @@
plugins {
id 'groowt-conventions'
id 'java-gradle-plugin'
id 'groowt-publish'
}
repositories {
mavenCentral()
}
dependencies {
implementation libs.groovy
implementation project(':groowt-gradle-model')
}
gradlePlugin {
plugins {
create('groowtGradle') {
id = 'groowt-gradle'
implementationClass = 'groowt.gradle.GroowtGradlePlugin'
}
}
}
publishing {
publications {
create('groowtGradlePlugin', MavenPublication) {
artifactId = 'groowt-gradle'
from components.java
}
}
}

View File

@ -0,0 +1,22 @@
package groowt.gradle;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.Property;
import javax.inject.Inject;
public class DefaultGroowtExtension implements GroowtExtension {
private final Property<String> basePackage;
@Inject
public DefaultGroowtExtension(ObjectFactory objectFactory) {
this.basePackage = objectFactory.property(String.class);
}
@Override
public Property<String> getBasePackage() {
return this.basePackage;
}
}

View File

@ -0,0 +1,19 @@
package groowt.gradle;
import org.gradle.api.file.SourceDirectorySet;
import org.gradle.api.internal.file.DefaultSourceDirectorySet;
import org.gradle.api.internal.tasks.TaskDependencyFactory;
import javax.inject.Inject;
public class DefaultTemplateSourceDirectorySet extends DefaultSourceDirectorySet implements TemplateSourceDirectorySet {
@Inject
public DefaultTemplateSourceDirectorySet(
SourceDirectorySet delegate,
TaskDependencyFactory taskDependencyFactory
) {
super(delegate, taskDependencyFactory);
}
}

View File

@ -0,0 +1,61 @@
package groowt.gradle;
import groovy.lang.Closure;
import org.gradle.api.Action;
import org.gradle.api.file.SourceDirectorySet;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.reflect.HasPublicType;
import org.gradle.api.reflect.TypeOf;
import org.gradle.util.internal.ConfigureUtil;
import javax.inject.Inject;
public class DefaultTemplateSourceSet implements TemplateSourceSet, HasPublicType {
private final TemplateSourceDirectorySet templateSourceDirectorySet;
private final SourceDirectorySet allTemplates;
@Inject
public DefaultTemplateSourceSet(ObjectFactory objectFactory, String name, String displayName) {
this.templateSourceDirectorySet = objectFactory.newInstance(
DefaultTemplateSourceDirectorySet.class,
objectFactory.sourceDirectorySet(name, displayName + " ComponentTemplate sources")
);
this.templateSourceDirectorySet.getFilter().include("**/*.wvc", "**/*.gst");
this.allTemplates = objectFactory.sourceDirectorySet(
"all" + name,
displayName + " ComponentTemplate sources"
);
this.allTemplates.source(this.templateSourceDirectorySet);
this.allTemplates.getFilter().include("**/*.wvc", "**/*.gst");
}
@Override
public TypeOf<?> getPublicType() {
return TypeOf.typeOf(TemplateSourceSet.class);
}
@Override
public TemplateSourceDirectorySet getTemplates() {
return this.templateSourceDirectorySet;
}
@Override
public TemplateSourceSet templates(Action<? super TemplateSourceDirectorySet> action) {
action.execute(this.templateSourceDirectorySet);
return this;
}
@SuppressWarnings("rawtypes")
@Override
public TemplateSourceSet templates(Closure closure) {
ConfigureUtil.configure(closure, this.templateSourceDirectorySet);
return this;
}
@Override
public SourceDirectorySet getAllTemplates() {
return this.allTemplates;
}
}

View File

@ -0,0 +1,7 @@
package groowt.gradle;
import org.gradle.api.provider.Property;
public interface GroowtExtension {
Property<String> getBasePackage();
}

View File

@ -0,0 +1,91 @@
package groowt.gradle;
import groowt.gradle.model.GroowtGradleModelBuilder;
import org.gradle.api.Plugin;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.file.FileCollection;
import org.gradle.api.internal.tasks.DefaultSourceSet;
import org.gradle.api.plugins.GroovyPlugin;
import org.gradle.api.plugins.JavaPlugin;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.SourceSetContainer;
import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry;
import javax.inject.Inject;
import static org.gradle.api.internal.lambdas.SerializableLambdas.spec;
public class GroowtGradlePlugin implements Plugin<Project> {
private final ToolingModelBuilderRegistry modelBuilderRegistry;
@Inject
public GroowtGradlePlugin(ToolingModelBuilderRegistry modelBuilderRegistry) {
this.modelBuilderRegistry = modelBuilderRegistry;
}
@Override
public void apply(Project project) {
// Apply java and groovy plugins, if not done already
final var pluginManager = project.getPluginManager();
pluginManager.apply(JavaPlugin.class);
pluginManager.apply(GroovyPlugin.class);
// Create our groowt configuration for storing the groowt dependencies
final Provider<Configuration> groowtConfigurationProvider = project.getConfigurations()
.register("groowt", configuration -> {
configuration.setCanBeConsumed(false);
configuration.setCanBeResolved(true);
});
// Create groowt extension and source sets.
final GroowtExtension groowtExtension = project.getExtensions().create(
GroowtExtension.class,
"groowt",
DefaultGroowtExtension.class
);
groowtExtension.getBasePackage().convention("");
final JavaPluginExtension javaExtension = project.getExtensions().getByType(JavaPluginExtension.class);
final SourceSetContainer javaSourceSets = javaExtension.getSourceSets();
// data resources, such as texts, json files, sqlite-databases, etc.
javaSourceSets.getByName("main", mainSourceSet -> {
mainSourceSet.getResources().srcDir("src/data");
});
// TODO: figure out how we can set the compile behavior for all of these.
javaSourceSets.forEach(sourceSet -> {
final TemplateSourceSet templateSourceSet = project.getObjects().newInstance(
DefaultTemplateSourceSet.class,
"wvc",
((DefaultSourceSet) sourceSet).getDisplayName()
);
final TemplateSourceDirectorySet templateSourceDirectorySet = templateSourceSet.getTemplates();
sourceSet.getExtensions().add(
TemplateSourceDirectorySet.class,
"templates",
templateSourceDirectorySet
);
templateSourceDirectorySet.srcDir("src/" + sourceSet.getName() + "/templates");
// Explicitly capture only a FileCollection in the lambda below for compatibility with configuration-cache.
@SuppressWarnings("UnnecessaryLocalVariable")
final FileCollection templateSourceFiles = templateSourceDirectorySet;
sourceSet.getResources().getFilter().exclude(
spec(element -> templateSourceFiles.contains(element.getFile()))
);
sourceSet.getAllJava().source(templateSourceDirectorySet);
sourceSet.getAllSource().source(templateSourceDirectorySet);
});
// create init task
project.getTasks().create("groowtInit", GroowtInitTask.class, groowtConfigurationProvider);
// tooling models for cli
this.modelBuilderRegistry.register(new GroowtGradleModelBuilder());
}
}

View File

@ -0,0 +1,95 @@
package groowt.gradle;
import org.gradle.api.DefaultTask;
import org.gradle.api.Project;
import org.gradle.api.artifacts.Configuration;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.Provider;
import org.gradle.api.tasks.TaskAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.io.*;
import java.util.Set;
public class GroowtInitTask extends DefaultTask {
private static final Logger logger = LoggerFactory.getLogger(GroowtInitTask.class);
private static final Set<String> srcDirsToMake = Set.of("groovy", "templates");
private final Provider<Configuration> groowtConfigurationProvider;
private final File binDir;
private final File groowtDir;
@Inject
public GroowtInitTask(Project project, Provider<Configuration> groowtConfigurationProvider) {
this.groowtConfigurationProvider = groowtConfigurationProvider;
this.binDir = new File(project.getRootDir(), "bin");
this.groowtDir = new File(project.getRootDir(), "groowt");
}
protected void createBin() throws IOException {
//noinspection ResultOfMethodCallIgnored
this.binDir.mkdirs();
final var groowtFile = new File(this.binDir, "groowt");
try (final InputStream bootstrapInputStream = this.getClass().getResourceAsStream("groowt")) {
if (bootstrapInputStream != null) {
try (final OutputStream bootstrapOutputStream = new FileOutputStream(groowtFile)) {
bootstrapInputStream.transferTo(bootstrapOutputStream);
}
if (!groowtFile.setExecutable(true)) {
logger.warn("Could not set bin/groowt to executable; you will have to do this yourself.");
}
} else {
throw new RuntimeException("Could not find groowt shell script.");
}
}
}
protected void createGroowtFolder() throws IOException {
//noinspection ResultOfMethodCallIgnored
this.groowtDir.mkdirs();
final var groowtConfiguration = this.groowtConfigurationProvider.get();
final Set<File> groowtCliFiles = groowtConfiguration.files(dependency -> {
final var group = dependency.getGroup();
if (group == null || !group.equals("groowt")) {
return false;
} else {
return dependency.getName().equals("groowt-cli");
}
});
final File groowtCliJarFile = groowtCliFiles.stream()
.filter(file -> file.getName().endsWith(".jar"))
.findFirst()
.orElseThrow(() -> new RuntimeException("Could not find groowt-cli jar file."));
final File groowtCliJarOutputFile = new File(this.groowtDir, "groowt-cli.jar");
try (final InputStream groowtCliJarInputStream = new FileInputStream(groowtCliJarFile)) {
try (final OutputStream groowtCliJarOutputStream = new FileOutputStream(groowtCliJarOutputFile)) {
groowtCliJarInputStream.transferTo(groowtCliJarOutputStream);
}
}
}
protected void createSrcDirs() {
final var javaPluginExtension = this.getProject().getExtensions().getByType(JavaPluginExtension.class);
javaPluginExtension.getSourceSets().forEach(sourceSet -> {
final var srcDirs = sourceSet.getAllSource().getSrcDirs();
srcDirs.forEach(srcDir -> {
if (!sourceSet.getName().contains("test") && srcDirsToMake.contains(srcDir.getName())) {
//noinspection ResultOfMethodCallIgnored
srcDir.mkdirs();
}
});
});
}
@TaskAction
public void doInit() throws IOException {
this.createBin();
this.createGroowtFolder();
this.createSrcDirs();
}
}

View File

@ -0,0 +1,5 @@
package groowt.gradle;
import org.gradle.api.file.SourceDirectorySet;
public interface TemplateSourceDirectorySet extends SourceDirectorySet {}

View File

@ -0,0 +1,18 @@
package groowt.gradle;
import groovy.lang.Closure;
import org.gradle.api.Action;
import org.gradle.api.file.SourceDirectorySet;
public interface TemplateSourceSet {
TemplateSourceDirectorySet getTemplates();
SourceDirectorySet getAllTemplates();
TemplateSourceSet templates(Action<? super TemplateSourceDirectorySet> action);
@SuppressWarnings("rawtypes")
TemplateSourceSet templates(Closure closure);
}

View File

@ -0,0 +1,50 @@
package groowt.gradle.model;
import groowt.gradle.GroowtExtension;
import groowt.gradle.TemplateSourceDirectorySet;
import org.gradle.api.Project;
import org.gradle.api.plugins.JavaPluginExtension;
import org.gradle.api.provider.Property;
import org.gradle.tooling.provider.model.ToolingModelBuilder;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class GroowtGradleModelBuilder implements ToolingModelBuilder {
@Override
public boolean canBuild(String modelName) {
return modelName.equals(GroowtGradleModel.class.getName());
}
@Override
public @NotNull Object buildAll(@NotNull String modelName, Project project) {
final DefaultGroowtGradleModel model = new DefaultGroowtGradleModel();
final var groowtExtension = project.getExtensions().getByType(GroowtExtension.class);
// base package
final Property<String> basePackage = groowtExtension.getBasePackage();
if (!basePackage.isPresent()) {
throw new RuntimeException(
"The property 'basePackage' must be set under the 'groowt' extension in build.gradle"
);
}
model.setBasePackage(basePackage.get());
// templates dirs
final Map<String, Set<File>> sourceSetToTemplatesDirs = new HashMap<>();
final var javaExtension = project.getExtensions().getByType(JavaPluginExtension.class);
javaExtension.getSourceSets().forEach(sourceSet -> {
final TemplateSourceDirectorySet templateSourceDirectorySet =
sourceSet.getExtensions().getByType(TemplateSourceDirectorySet.class);
sourceSetToTemplatesDirs.put(sourceSet.getName(), templateSourceDirectorySet.getFiles());
});
model.setSourceFileSets(sourceSetToTemplatesDirs);
return model;
}
}

View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
java -jar groowt/groowt-cli.jar "$@"

View File

@ -6,7 +6,8 @@ pluginManagement {
rootProject.name = 'groowt'
include 'views', 'view-components', 'web-view-components', 'web-view-components-compiler'
include 'cli', 'groowt-all', 'groowt-gradle', 'groowt-gradle-model', 'views', 'view-components',
'web-view-components', 'web-view-components-compiler'
file('util').eachDir {
include it.name

View File

@ -24,7 +24,7 @@ jar {
publishing {
publications {
create('di', MavenPublication) {
artifactId = 'util-di'
artifactId = 'groowt-util-di'
from components.java
}
}

View File

@ -3,58 +3,31 @@ package groowt.util.di;
import org.jetbrains.annotations.Nullable;
import java.lang.annotation.Annotation;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
public class DefaultRegistry implements Registry {
protected static class BindingContainer {
protected record ClassKeyBinding<T>(Class<T> key, Binding<T> binding) {}
private final Map<Class<?>, Binding<?>> bindings = new HashMap<>();
@SuppressWarnings("unchecked")
public <T> @Nullable Binding<T> get(Class<T> key) {
for (final var entry : bindings.entrySet()) {
if (entry.getKey().isAssignableFrom(key)) {
return (Binding<T>) entry.getValue();
}
}
return null;
}
public <T> void put(Class<T> key, Binding<T> binding) {
this.bindings.put(key, binding);
}
public void remove(Class<?> key) {
this.bindings.remove(key);
}
public <T> void removeIf(Class<T> key, Predicate<? super Binding<T>> filter) {
if (filter.test(this.get(key))) {
this.bindings.remove(key);
}
}
public void clear() {
this.bindings.clear();
}
}
protected final BindingContainer bindingContainer = new BindingContainer();
protected final Collection<ClassKeyBinding<?>> classBindings = new ArrayList<>();
protected final Collection<RegistryExtension> extensions = new ArrayList<>();
@Override
public void removeBinding(Class<?> key) {
this.bindingContainer.remove(key);
this.classBindings.removeIf(classKeyBinding -> classKeyBinding.key().equals(key));
}
@SuppressWarnings("unchecked")
@Override
public <T> void removeBindingIf(Class<T> key, Predicate<Binding<T>> filter) {
this.bindingContainer.removeIf(key, filter);
this.classBindings.removeIf(classKeyBinding ->
classKeyBinding.key().equals(key) && filter.test((Binding<T>) classKeyBinding.binding())
);
}
private <E extends RegistryExtension> List<E> getAllRegistryExtensions(Class<E> extensionType) {
@ -144,12 +117,18 @@ public class DefaultRegistry implements Registry {
public <T> void bind(Class<T> key, Consumer<? super BindingConfigurator<T>> configure) {
final var configurator = new SimpleBindingConfigurator<>(key);
configure.accept(configurator);
this.bindingContainer.put(key, configurator.getBinding());
this.classBindings.add(new ClassKeyBinding<>(key, configurator.getBinding()));
}
@SuppressWarnings("unchecked")
@Override
public <T> @Nullable Binding<T> getBinding(Class<T> key) {
return this.bindingContainer.get(key);
public @Nullable <T> Binding<T> getBinding(Class<T> key) {
for (final var classKeyBinding : this.classBindings) {
if (key.isAssignableFrom(classKeyBinding.key())) {
return (Binding<T>) classKeyBinding.binding();
}
}
return null;
}
private KeyBinder<?> findKeyBinder(Class<?> keyClass) {
@ -210,7 +189,7 @@ public class DefaultRegistry implements Registry {
@Override
public void clearAllBindings() {
this.bindingContainer.clear();
this.classBindings.clear();
for (final var extension : this.extensions) {
if (extension instanceof KeyBinder<?> keyBinder) {
keyBinder.clearAllBindings();

View File

@ -1,9 +1,8 @@
package groowt.util.di;
import jakarta.inject.Provider;
import jakarta.inject.Singleton;
import static groowt.util.di.BindingUtil.toLazySingleton;
import static groowt.util.di.BindingUtil.toSingleton;
public final class SingletonScopeHandler implements ScopeHandler<Singleton> {
@ -20,22 +19,12 @@ public final class SingletonScopeHandler implements ScopeHandler<Singleton> {
RegistryObjectFactory objectFactory
) {
final Binding<T> potentialBinding = this.owner.getBinding(dependencyClass);
return switch (potentialBinding) {
case ClassBinding<T>(Class<T> from, Class<? extends T> to) -> {
this.owner.bind(from, toLazySingleton(() -> objectFactory.createInstance(to)));
yield this.owner.getBinding(from);
if (potentialBinding != null) {
return potentialBinding;
} else {
this.owner.bind(dependencyClass, toSingleton(objectFactory.createInstance(dependencyClass)));
return this.owner.getBinding(dependencyClass);
}
case ProviderBinding<T>(Class<T> from, Provider<? extends T> provider) -> {
this.owner.bind(from, toLazySingleton(provider::get));
yield this.owner.getBinding(from);
}
case SingletonBinding<T> singletonBinding -> singletonBinding;
case LazySingletonBinding<T> lazySingletonBinding -> lazySingletonBinding;
case null -> {
this.owner.bind(dependencyClass, toLazySingleton(() -> objectFactory.createInstance(dependencyClass)));
yield this.owner.getBinding(dependencyClass);
}
};
}
@Override

View File

@ -2,7 +2,6 @@ package groowt.util.di;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.inject.Singleton;
import org.junit.jupiter.api.Test;
import static groowt.util.di.BindingUtil.*;
@ -251,25 +250,4 @@ public class DefaultRegistryObjectFactoryTests {
assertEquals("Given Greeting", g.greet());
}
@Singleton
public static final class SingletonGreeter implements Greeter {
@Override
public String greet() {
return "Hello, World!";
}
}
@Test
public void singletonDoesNotOverflow() {
final var b = DefaultRegistryObjectFactory.Builder.withDefaults();
b.configureRegistry(r -> {
r.bind(SingletonGreeter.class, toSelf());
});
final var f = b.build();
final var g = f.get(SingletonGreeter.class);
assertEquals("Hello, World!", g.greet());
}
}

View File

@ -19,7 +19,7 @@ jar {
publishing {
publications {
create('extensible', MavenPublication) {
artifactId = 'util-extensible'
artifactId = 'groowt-util-extensible'
from components.java
}
}

View File

@ -24,7 +24,7 @@ jar {
publishing {
publications {
create('fp', MavenPublication) {
artifactId = 'util-fp'
artifactId = 'groowt-util-fp'
from components.java
}
}

View File

@ -22,14 +22,10 @@ java {
withSourcesJar()
}
jar {
archiveBaseName = 'groowt-view-components'
}
publishing {
publications {
create('viewComponents', MavenPublication) {
artifactId = 'view-components'
artifactId = 'groowt-view-components'
from components.java
}
}

View File

@ -10,8 +10,41 @@ public abstract class CachingComponentTemplateCompiler<U extends ComponentTempla
private final Map<Class<? extends ViewComponent>, ComponentTemplateCompileResult> cache = new HashMap<>();
// private ComponentTemplate instantiate(
// GroovyClassLoader groovyClassLoader,
// CompileResult compileResult
// ) {
// for (final var groovyClass : compileResult.otherClasses()) {
// // Try to find it. If we can't, we need to load it via the groovy loader
// try {
// Class.forName(groovyClass.getName(), true, groovyClassLoader);
// } catch (ClassNotFoundException ignored) {
// groovyClassLoader.defineClass(groovyClass.getName(), groovyClass.getBytes());
// } catch (LinkageError ignored) {
// // no-op, because we already have it
// }
// }
// final GroovyClass templateGroovyClass = compileResult.templateClass();
// Class<?> templateClass;
// // Try to find it. If we can't, we need to load it via the groovy loader
// try {
// templateClass = Class.forName(templateGroovyClass.getName(), true, groovyClassLoader);
// } catch (ClassNotFoundException ignored) {
// templateClass = groovyClassLoader.defineClass(
// templateGroovyClass.getName(),
// templateGroovyClass.getBytes()
// );
// }
// try {
// return (ComponentTemplate) templateClass.getConstructor().newInstance();
// } catch (Exception e) {
// throw new RuntimeException("Unable to instantiate ComponentTemplate " + templateClass.getName(), e);
// }
// }
@Override
public final ComponentTemplateCompileResult compile(U compileUnit) throws ComponentTemplateCompileException {
public final ComponentTemplateCompileResult compile(U compileUnit)
throws ComponentTemplateCompileException {
if (this.cache.containsKey(compileUnit.getForClass())) {
return this.cache.get(compileUnit.getForClass());
} else {

View File

@ -4,9 +4,16 @@ import groowt.view.component.ViewComponent;
import groowt.view.component.compiler.source.ComponentTemplateSource;
public interface ComponentTemplateCompileUnit {
String getDescriptiveName();
Class<? extends ViewComponent> getForClass();
String getDefaultPackageName();
ComponentTemplateSource getSource();
ComponentTemplateCompileResult compile() throws ComponentTemplateCompileException;
ComponentTemplateCompileResult compile(ComponentTemplateCompilerConfiguration configuration)
throws ComponentTemplateCompileException;
default ComponentTemplateCompileResult compile() throws ComponentTemplateCompileException {
return this.compile(new DefaultComponentTemplateCompilerConfiguration());
}
}

View File

@ -0,0 +1,11 @@
package groowt.view.component.compiler;
import groovy.lang.GroovyClassLoader;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.CompilerConfiguration;
public interface ComponentTemplateCompilerConfiguration {
GroovyClassLoader getGroovyClassLoader();
CompilerConfiguration getGroovyCompilerConfiguration();
CompilePhase getToCompilePhase();
}

View File

@ -0,0 +1,48 @@
package groowt.view.component.compiler;
import groovy.lang.GroovyClassLoader;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.CompilerConfiguration;
import static java.util.Objects.requireNonNull;
public class DefaultComponentTemplateCompilerConfiguration implements ComponentTemplateCompilerConfiguration {
private GroovyClassLoader groovyClassLoader;
private CompilerConfiguration groovyCompilerConfiguration;
private CompilePhase toCompilePhase;
public DefaultComponentTemplateCompilerConfiguration() {
this.groovyClassLoader = new GroovyClassLoader(Thread.currentThread().getContextClassLoader());
this.groovyCompilerConfiguration = new CompilerConfiguration();
this.toCompilePhase = CompilePhase.CLASS_GENERATION;
}
@Override
public GroovyClassLoader getGroovyClassLoader() {
return this.groovyClassLoader;
}
public void setGroovyClassLoader(GroovyClassLoader groovyClassLoader) {
this.groovyClassLoader = requireNonNull(groovyClassLoader);
}
@Override
public CompilerConfiguration getGroovyCompilerConfiguration() {
return this.groovyCompilerConfiguration;
}
public void setGroovyCompilerConfiguration(CompilerConfiguration groovyCompilerConfiguration) {
this.groovyCompilerConfiguration = requireNonNull(groovyCompilerConfiguration);
}
@Override
public CompilePhase getToCompilePhase() {
return this.toCompilePhase;
}
public void setToCompilePhase(CompilePhase toCompilePhase) {
this.toCompilePhase = requireNonNull(toCompilePhase);
}
}

View File

@ -99,23 +99,6 @@ public interface ComponentContext {
return ancestorClass.cast(this.findNearestAncestor(matching.and(ancestorClass::isInstance)));
}
default <T extends ViewComponent> @Nullable T findNearestAncestor(Class<T> ancestorClass) {
return ancestorClass.cast(this.findNearestAncestor(ancestorClass::isInstance));
}
boolean hasAncestor(Predicate<? super ViewComponent> matching);
default <T extends ViewComponent> boolean hasAncestor(
Class<T> ancestorClass,
Predicate<? super ViewComponent> matching
) {
return this.hasAncestor(matching.and(ancestorClass::isInstance));
}
default <T extends ViewComponent> boolean hasAncestor(Class<T> ancestorClass) {
return this.hasAncestor(ancestorClass::isInstance);
}
List<ViewComponent> getAllAncestors();
}

View File

@ -70,10 +70,7 @@ public class DefaultComponentContext implements ComponentContext {
public @Nullable ViewComponent findNearestAncestor(Predicate<? super ViewComponent> matching) {
final List<ViewComponent> componentStack = this.getRenderContext().getComponentStack();
if (componentStack.size() > 1) {
// 1/27/25: earlier this was originally componentStack.size() - 1 as the second argument to sublist().
// On examination today, it didn't make sense, because it would be chopping off the farthest ancestor from
// search. So I removed it, in accordance with the implementation in hasAncestor().
for (final var ancestor : componentStack.subList(1, componentStack.size())) {
for (final var ancestor : componentStack.subList(1, componentStack.size() -1)) {
if (matching.test(ancestor)) {
return ancestor;
}
@ -82,19 +79,6 @@ public class DefaultComponentContext implements ComponentContext {
return null;
}
@Override
public boolean hasAncestor(Predicate<? super ViewComponent> matching) {
final List<ViewComponent> componentStack = this.getRenderContext().getComponentStack();
if (componentStack.size() > 1) {
for (final var ancestor : componentStack.subList(1, componentStack.size())) {
if (matching.test(ancestor)) {
return true;
}
}
}
return false;
}
@Override
public List<ViewComponent> getAllAncestors() {
final List<ViewComponent> componentStack = this.getRenderContext().getComponentStack();

View File

@ -25,14 +25,10 @@ java {
withSourcesJar()
}
jar {
archiveBaseName = 'groowt-views'
}
publishing {
publications {
create('views', MavenPublication) {
artifactId = 'views'
artifactId = 'groowt-views'
from components.java
}
}

View File

@ -199,9 +199,7 @@ tasks.register('uberJar', Jar) {
group 'groovyc'
archiveBaseName = 'web-view-components-uber'
from sourceSets.main.output
from sourceSets.main.runtimeClasspath
.filter(File.&exists)
.collect { it.isDirectory() ? it : zipTree(it) }
from sourceSets.main.runtimeClasspath.filter(File.&exists).collect { it.isDirectory() ? it : zipTree(it) }
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
}
@ -219,10 +217,6 @@ tasks.named('sourcesJar', Jar) {
dependsOn 'generateAllAntlr'
}
jar {
archiveBaseName = 'groowt-web-view-components-compiler'
}
publishing {
publications {
create('webViewComponentsCompiler', MavenPublication) {

View File

@ -4,15 +4,14 @@ if [ "$1" == "--debug" ]; then
shift
gradle -q uberJar && \
java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:8192 \
-cp build/libs/web-view-components-uber-0.1.2.jar \
-cp build/libs/web-view-components-uber-0.1.0.jar \
org.codehaus.groovy.tools.FileSystemCompiler \
--configscript src/main/resources/groowt/view/component/web/groovyc/groovycConfigurationScript.groovy \
-d groovyc-out \
"$@"
else
gradle -q uberJar && \
java -cp build/libs/web-view-components-uber-0.1.2.jar \
org.codehaus.groovy.tools.FileSystemCompiler \
groovyc -cp build/libs/web-view-components-uber-0.1.0.jar \
--configscript src/main/resources/groowt/view/component/web/groovyc/groovycConfigurationScript.groovy \
-d groovyc-out \
"$@"

View File

@ -1 +0,0 @@
<Echo>Hello, World!</Echo>

View File

@ -307,7 +307,7 @@ AttributeIdentifierStartChar
fragment
AttributeIdentifierChar
: [-\p{L}_$0-9]
: [\p{L}_$0-9]
;
Equals

View File

@ -4,6 +4,7 @@ import groowt.view.component.compiler.*;
import groowt.view.component.web.WebViewComponentBugError;
import groowt.view.component.web.ast.node.CompilationUnitNode;
import groowt.view.component.web.transpile.DefaultGroovyTranspiler;
import org.antlr.v4.runtime.ParserRuleContext;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.tools.GroovyClass;
@ -15,12 +16,25 @@ public class DefaultWebViewComponentTemplateCompiler
extends CachingComponentTemplateCompiler<WebViewComponentTemplateCompileUnit>
implements WebViewComponentTemplateCompiler {
private final WebViewComponentTemplateCompilerConfiguration configuration;
private final ComponentTemplateCompilerConfiguration configuration;
public DefaultWebViewComponentTemplateCompiler(WebViewComponentTemplateCompilerConfiguration configuration) {
public DefaultWebViewComponentTemplateCompiler(ComponentTemplateCompilerConfiguration configuration) {
this.configuration = configuration;
}
protected WebViewComponentTemplateCompileException getException(
WebViewComponentTemplateCompileUnit compileUnit,
ParserRuleContext parserRuleContext
) {
final var exception = new WebViewComponentTemplateCompileException(
compileUnit,
"Parser error: " + parserRuleContext.exception.getMessage(),
parserRuleContext.exception
);
exception.setParserRuleContext(parserRuleContext);
return exception;
}
@Override
protected ComponentTemplateCompileResult doCompile(WebViewComponentTemplateCompileUnit compileUnit)
throws ComponentTemplateCompileException {
@ -35,12 +49,22 @@ public class DefaultWebViewComponentTemplateCompiler
: "AnonymousWebViewComponent" + System.nanoTime();
final var templateClassSimpleName = ownerComponentName + "Template";
final SourceUnit sourceUnit = transpiler.transpile(compileUnit, cuNode, templateClassSimpleName);
final SourceUnit sourceUnit = transpiler.transpile(
this.configuration,
compileUnit,
cuNode,
templateClassSimpleName
);
compileUnit.getGroovyCompilationUnit().addSource(sourceUnit);
// set the groovy compile unit's class loader to the configuration's classloader.
compileUnit.getGroovyCompilationUnit().setClassLoader(
this.configuration.getGroovyClassLoader()
);
// compile groovy
try {
compileUnit.getGroovyCompilationUnit().compile(this.configuration.getCompilePhase().getPhaseNumber());
compileUnit.getGroovyCompilationUnit().compile(this.configuration.getToCompilePhase().getPhaseNumber());
} catch (CompilationFailedException compilationFailedException) {
throw new WebViewComponentTemplateCompileException(
compileUnit,

View File

@ -1,9 +1,11 @@
package groowt.view.component.web.compiler;
import groowt.view.component.compiler.ComponentTemplateCompilerConfiguration;
public class DefaultWebViewComponentTemplateCompilerFactory implements WebViewComponentTemplateCompilerFactory {
@Override
public WebViewComponentTemplateCompiler create(WebViewComponentTemplateCompilerConfiguration configuration) {
public WebViewComponentTemplateCompiler create(ComponentTemplateCompilerConfiguration configuration) {
return new DefaultWebViewComponentTemplateCompiler(configuration);
}

View File

@ -1,6 +1,7 @@
package groowt.view.component.web.groovyc;
import groowt.view.component.compiler.ComponentTemplateCompileException;
import groowt.view.component.compiler.DefaultComponentTemplateCompilerConfiguration;
import groowt.view.component.compiler.source.ComponentTemplateSource;
import groowt.view.component.web.WebViewComponentBugError;
import groowt.view.component.web.ast.node.CompilationUnitNode;
@ -79,12 +80,13 @@ public class DelegatingWebViewComponentTemplateParserPlugin implements ParserPlu
}
final var groovyTranspiler = new DefaultGroovyTranspiler();
final String templateClassName = sourceUnitFileName.substring(0, sourceUnitFileName.length() - 4);
final String teplateClassSimpleName = sourceUnitFileName.substring(0, sourceUnitFileName.length() - 4);
try {
final SourceUnit transpiledSourceUnit = groovyTranspiler.transpile(
new DefaultComponentTemplateCompilerConfiguration(),
compileUnit,
cuNode,
templateClassName
teplateClassSimpleName
);
return transpiledSourceUnit.getAST();
} catch (ComponentTemplateCompileException e) {

View File

@ -6,6 +6,7 @@ import groowt.view.component.web.WebViewComponentBugError;
import groowt.view.component.web.ast.node.*;
import groowt.view.component.web.transpile.groovy.GroovyUtil;
import groowt.view.component.web.transpile.groovy.GroovyUtil.ConvertResult;
import groowt.view.component.web.transpile.resolve.ComponentClassNodeResolver;
import groowt.view.component.web.util.SourcePosition;
import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.ast.expr.*;
@ -14,6 +15,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import static groowt.view.component.web.transpile.TranspilerUtil.*;
@ -23,9 +25,13 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
private static final ClassNode COMPONENT_RESOLVE_EXCEPTION_TYPE = ClassHelper.make(ComponentResolveException.class);
private static final ClassNode COMPONENT_CREATE_EXCEPTION_TYPE = ClassHelper.make(ComponentCreateException.class);
private static final Pattern isFqn = Pattern.compile("^(\\p{Ll}.+\\.)+\\p{Lu}.+$");
private static final Pattern isWithPackage = Pattern.compile("^\\p{Ll}.+\\.");
private LeftShiftFactory leftShiftFactory;
private ValueNodeTranspiler valueNodeTranspiler;
private BodyTranspiler bodyTranspiler;
private ComponentClassNodeResolver componentClassNodeResolver;
public void setLeftShiftFactory(LeftShiftFactory leftShiftFactory) {
this.leftShiftFactory = leftShiftFactory;
@ -39,6 +45,10 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
this.bodyTranspiler = bodyTranspiler;
}
public void setComponentClassNodeResolver(ComponentClassNodeResolver componentClassNodeResolver) {
this.componentClassNodeResolver = componentClassNodeResolver;
}
/* UTIL */
protected String getComponentName(int componentNumber) {
@ -64,14 +74,49 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
/* RESOLVE */
protected List<Expression> getArgsAsList(TypedComponentNode componentNode) {
protected List<Expression> getArgsAsList(
TypedComponentNode componentNode,
TranspilerState state
) {
return switch (componentNode.getArgs().getType()) {
case ClassComponentTypeNode classComponentTypeNode -> {
final String identifier = classComponentTypeNode.getIdentifier();
final ConstantExpression alias = getStringLiteral(identifier);
final var matcher = isFqn.matcher(identifier);
if (matcher.matches()) {
final ClassNode classNode = ClassHelper.make(identifier);
final var classExpression = new ClassExpression(classNode);
final ClassExpression classExpression = new ClassExpression(classNode);
yield List.of(alias, classExpression);
} else {
// we need to resolve it
final var isWithPackageMatcher = isWithPackage.matcher(identifier);
if (isWithPackageMatcher.matches()) {
final var resolveResult = this.componentClassNodeResolver.getClassForFqn(identifier);
if (resolveResult.isLeft()) {
final var error = resolveResult.getLeft();
error.setNode(componentNode.getArgs().getType());
state.addError(error);
yield List.of();
} else {
final ClassNode classNode = resolveResult.getRight();
final ClassExpression classExpression = new ClassExpression(classNode); // TODO: pos
yield List.of(alias, classExpression);
}
} else {
final var resolveResult =
this.componentClassNodeResolver.getClassForNameWithoutPackage(identifier);
if (resolveResult.isLeft()) {
final var error = resolveResult.getLeft();
error.setNode(componentNode.getArgs().getType());
state.addError(error);
yield List.of();
} else {
final ClassNode classNode = resolveResult.getRight();
final ClassExpression classExpression = new ClassExpression(classNode); // TODO: pos
yield List.of(alias, classExpression);
}
}
}
}
case StringComponentTypeNode stringComponentTypeNode -> {
final String identifier = stringComponentTypeNode.getIdentifier();
@ -82,8 +127,8 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
}
// 'h1' | 'MyComponent', MyComponent(.class)
protected ArgumentListExpression getResolveArgs(TypedComponentNode componentNode) {
final List<Expression> args = this.getArgsAsList(componentNode);
protected ArgumentListExpression getResolveArgs(TypedComponentNode componentNode, TranspilerState state) {
final List<Expression> args = this.getArgsAsList(componentNode, state);
final ArgumentListExpression argsListExpr = new ArgumentListExpression();
args.forEach(argsListExpr::addExpression);
return argsListExpr;
@ -97,7 +142,7 @@ public class DefaultComponentTranspiler implements ComponentTranspiler {
return new MethodCallExpression(
new VariableExpression(state.getRenderContext()),
"resolve",
this.getResolveArgs(componentNode)
this.getResolveArgs(componentNode, state)
);
}

View File

@ -2,6 +2,7 @@ package groowt.view.component.web.transpile;
import groowt.view.component.compiler.ComponentTemplateCompileException;
import groowt.view.component.compiler.ComponentTemplateCompileUnit;
import groowt.view.component.compiler.ComponentTemplateCompilerConfiguration;
import groowt.view.component.web.ast.node.BodyNode;
import groowt.view.component.web.ast.node.CompilationUnitNode;
import groowt.view.component.web.ast.node.PreambleNode;
@ -9,6 +10,7 @@ import groowt.view.component.web.compiler.MultipleWebViewComponentCompileErrorsE
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileException;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit;
import groowt.view.component.web.transpile.groovy.GroovyUtil;
import groowt.view.component.web.transpile.resolve.ClassLoaderComponentClassNodeResolver;
import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.ast.stmt.BlockStatement;
@ -29,13 +31,25 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
private static final Logger logger = LoggerFactory.getLogger(DefaultGroovyTranspiler.class);
protected TranspilerConfiguration getConfiguration() {
return SimpleTranspilerConfiguration.withDefaults();
protected TranspilerConfiguration getConfiguration(
ClassLoaderComponentClassNodeResolver classLoaderComponentClassNodeResolver
) {
return SimpleTranspilerConfiguration.withDefaults(classLoaderComponentClassNodeResolver);
}
protected void addAutomaticImports(WebViewComponentModuleNode moduleNode, TranspilerConfiguration configuration) {
configuration.getImports().forEach(moduleNode::addImport);
configuration.getStaticImports().forEach(staticImport -> moduleNode.addStaticImport(
staticImport.getV1(), staticImport.getV2(), staticImport.getV3()
));
configuration.getStarImports().forEach(moduleNode::addStarImport);
configuration.getStaticStarImports().forEach(moduleNode::addStaticStarImport);
}
protected WebViewComponentModuleNode initModuleNode(
ComponentTemplateCompileUnit compileUnit,
WebViewComponentSourceUnit sourceUnit
WebViewComponentSourceUnit sourceUnit,
TranspilerConfiguration configuration
) {
final var moduleNode = new WebViewComponentModuleNode(sourceUnit);
sourceUnit.setModuleNode(moduleNode);
@ -45,16 +59,22 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
moduleNode.setPackageName(defaultPackageName);
}
this.addAutomaticImports(moduleNode, configuration);
return moduleNode;
}
protected ClassNode initMainClassNode(ComponentTemplateCompileUnit compileUnit, String templateClassName) {
protected ClassNode initMainClassNode(
ComponentTemplateCompileUnit compileUnit,
String templateClassName,
WebViewComponentModuleNode moduleNode
) {
final ClassNode mainClassNode = new ClassNode(
compileUnit.getDefaultPackageName() + templateClassName,
ACC_PUBLIC,
ClassHelper.OBJECT_TYPE
);
mainClassNode.addInterface(TranspilerUtil.COMPONENT_TEMPLATE);
moduleNode.addClass(mainClassNode);
return mainClassNode;
}
@ -196,32 +216,40 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
@Override
public WebViewComponentSourceUnit transpile(
ComponentTemplateCompilerConfiguration compilerConfiguration,
WebViewComponentTemplateCompileUnit compileUnit,
CompilationUnitNode compilationUnitNode,
String templateClassSimpleName
) throws ComponentTemplateCompileException {
// transpilerConfiguration, and positionSetter
final var transpilerConfiguration = this.getConfiguration();
// resolver, transpilerConfiguration, and positionSetter
final ClassLoaderComponentClassNodeResolver resolver = new ClassLoaderComponentClassNodeResolver(
compileUnit,
compilerConfiguration.getGroovyClassLoader()
);
final var transpilerConfiguration = this.getConfiguration(resolver);
final PositionSetter positionSetter = transpilerConfiguration.getPositionSetter();
// prepare sourceUnit
final CompilerConfiguration groovyCompilerConfiguration = compileUnit.getGroovyCompilationUnit()
.getConfiguration();
final CompilerConfiguration groovyCompilerConfiguration =
compilerConfiguration.getGroovyCompilerConfiguration();
final WebViewComponentSourceUnit sourceUnit = new WebViewComponentSourceUnit(
compileUnit.getDescriptiveName(),
compileUnit.getGroovyReaderSource(),
groovyCompilerConfiguration,
compileUnit.getGroovyCompilationUnit().getClassLoader(),
compilerConfiguration.getGroovyClassLoader(),
new ErrorCollector(groovyCompilerConfiguration)
);
// prepare moduleNode
final WebViewComponentModuleNode moduleNode = this.initModuleNode(
compileUnit, sourceUnit
compileUnit, sourceUnit, transpilerConfiguration
);
// set resolver's moduleNode
resolver.setModuleNode(moduleNode);
// prepare mainClassNode
final ClassNode mainClassNode = this.initMainClassNode(compileUnit, templateClassSimpleName);
final ClassNode mainClassNode = this.initMainClassNode(compileUnit, templateClassSimpleName, moduleNode);
// handle preamble
final PreambleNode preambleNode = compilationUnitNode.getPreambleNode();
@ -229,9 +257,6 @@ public class DefaultGroovyTranspiler implements GroovyTranspiler {
this.handlePreamble(templateClassSimpleName, preambleNode, mainClassNode, moduleNode, positionSetter);
}
// Moved here so that moduleNode#getMainClassName reflects fqn of mainClassName
moduleNode.addClass(mainClassNode);
// getRenderer method and render closure
// first, getRenderer params
final Parameter componentContextParam = new Parameter(COMPONENT_CONTEXT_TYPE, COMPONENT_CONTEXT_NAME);

View File

@ -1,12 +1,14 @@
package groowt.view.component.web.transpile;
import groowt.view.component.compiler.ComponentTemplateCompileException;
import groowt.view.component.compiler.ComponentTemplateCompilerConfiguration;
import groowt.view.component.web.ast.node.CompilationUnitNode;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit;
public interface GroovyTranspiler {
WebViewComponentSourceUnit transpile(
ComponentTemplateCompilerConfiguration compilerConfiguration,
WebViewComponentTemplateCompileUnit compileUnit,
CompilationUnitNode compilationUnitNode,
String templateClassSimpleName

View File

@ -1,6 +1,7 @@
package groowt.view.component.web.transpile;
import groovy.lang.Tuple3;
import groowt.view.component.web.transpile.resolve.ComponentClassNodeResolver;
import org.codehaus.groovy.ast.ClassNode;
import java.util.Map;
@ -11,8 +12,9 @@ import static groowt.view.component.web.transpile.TranspilerUtil.*;
public class SimpleTranspilerConfiguration implements TranspilerConfiguration {
public static TranspilerConfiguration withDefaults() {
public static TranspilerConfiguration withDefaults(ComponentClassNodeResolver componentClassNodeResolver) {
final var c = new SimpleTranspilerConfiguration();
c.setComponentClassNodeResolver(componentClassNodeResolver);
final var ct = new DefaultComponentTranspiler();
final PositionSetter ps = new SimplePositionSetter();
@ -25,6 +27,7 @@ public class SimpleTranspilerConfiguration implements TranspilerConfiguration {
ct.setLeftShiftFactory(lsf);
ct.setBodyTranspiler(bt);
ct.setValueNodeTranspiler(vnt);
ct.setComponentClassNodeResolver(componentClassNodeResolver);
c.setComponentTranspiler(ct);
c.setPositionSetter(ps);
@ -37,6 +40,7 @@ public class SimpleTranspilerConfiguration implements TranspilerConfiguration {
return c;
}
private ComponentClassNodeResolver componentClassNodeResolver;
private ComponentTranspiler componentTranspiler;
private PositionSetter positionSetter;
private LeftShiftFactory leftShiftFactory;
@ -45,6 +49,14 @@ public class SimpleTranspilerConfiguration implements TranspilerConfiguration {
private BodyTranspiler bodyTranspiler;
private ValueNodeTranspiler valueNodeTranspiler;
public ComponentClassNodeResolver getComponentClassNodeResolver() {
return Objects.requireNonNull(this.componentClassNodeResolver);
}
public void setComponentClassNodeResolver(ComponentClassNodeResolver componentClassNodeResolver) {
this.componentClassNodeResolver = componentClassNodeResolver;
}
public ComponentTranspiler getComponentTranspiler() {
return Objects.requireNonNull(this.componentTranspiler);
}

View File

@ -0,0 +1,60 @@
package groowt.view.component.web.transpile.resolve;
import groowt.util.fp.either.Either;
import groowt.view.component.web.WebViewComponent;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import java.util.ArrayList;
import java.util.List;
public class CachingComponentClassNodeResolver implements ComponentClassNodeResolver {
private final List<ClassNode> classNodes = new ArrayList<>();
protected final WebViewComponentTemplateCompileUnit compileUnit;
public CachingComponentClassNodeResolver(WebViewComponentTemplateCompileUnit compileUnit) {
this.compileUnit = compileUnit;
}
public void addClass(Class<? extends WebViewComponent> clazz) {
this.classNodes.add(ClassHelper.make(clazz));
}
public void addClassNode(ClassNode classNode) {
this.classNodes.add(classNode);
}
@Override
public Either<ClassNodeResolveException, ClassNode> getClassForFqn(String fqn) {
for (final var classNode : this.classNodes) {
if (classNode.getName().equals(fqn)) {
return Either.right(classNode);
}
}
return Either.left(new ClassNodeResolveException(
this.compileUnit,
fqn,
"Could not resolve ClassNode for fqn: " + fqn,
null
));
}
@Override
public Either<ClassNodeResolveException, ClassNode> getClassForNameWithoutPackage(String nameWithoutPackage) {
for (final var classNode : this.classNodes) {
if (classNode.getNameWithoutPackage().equals(nameWithoutPackage)) {
return Either.right(classNode);
}
}
return Either.left(new ClassNodeResolveException(
this.compileUnit,
nameWithoutPackage,
"Could not resolve ClassNode for nameWithoutPackage: " + nameWithoutPackage,
null
));
}
}

View File

@ -0,0 +1,53 @@
package groowt.view.component.web.transpile.resolve;
import groowt.util.fp.either.Either;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit;
import org.codehaus.groovy.ast.ClassNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ClassLoaderComponentClassNodeResolver extends ModuleNodeComponentClassNodeResolver {
private static final Logger logger = LoggerFactory.getLogger(ModuleNodeComponentClassNodeResolver.class);
protected final ClassLoader classLoader;
public ClassLoaderComponentClassNodeResolver(
WebViewComponentTemplateCompileUnit compileUnit,
ClassLoader classLoader
) {
super(compileUnit);
this.classLoader = classLoader;
}
protected final Either<ClassNodeResolveException, ClassNode> resolveWithClassLoader(String fqn) {
logger.debug("Trying to resolve {}", fqn);
try {
Class<?> clazz = this.classLoader.loadClass(ResolveUtil.convertCanonicalNameToBinaryName(fqn));
final var classNode = ResolveUtil.getClassNode(clazz);
return Either.right(classNode);
} catch (ClassNotFoundException classNotFoundException) {
return Either.left(
new ClassNodeResolveException(
this.compileUnit,
fqn,
"Could not find class " + fqn + " with classLoader " +
this.classLoader,
classNotFoundException
)
);
}
}
@Override
public Either<ClassNodeResolveException, ClassNode> getClassForFqn(String fqn) {
return super.getClassForFqn(fqn).flatMapLeft(ignored -> {
final var classLoaderResult = this.resolveWithClassLoader(fqn);
if (classLoaderResult.isRight()) {
this.addClassNode(classLoaderResult.getRight());
}
return classLoaderResult;
});
}
}

View File

@ -0,0 +1,42 @@
package groowt.view.component.web.transpile.resolve;
import groowt.util.fp.either.Either;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileException;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit;
import org.codehaus.groovy.ast.ClassNode;
public interface ComponentClassNodeResolver {
final class ClassNodeResolveException extends WebViewComponentTemplateCompileException {
private final String identifier;
public ClassNodeResolveException(
WebViewComponentTemplateCompileUnit compileUnit,
String identifier,
String message
) {
super(compileUnit, message);
this.identifier = identifier;
}
public ClassNodeResolveException(
WebViewComponentTemplateCompileUnit compileUnit,
String identifier,
String message,
Throwable cause
) {
super(compileUnit, message, cause);
this.identifier = identifier;
}
public String getIdentifier() {
return this.identifier;
}
}
Either<ClassNodeResolveException, ClassNode> getClassForFqn(String fqn);
Either<ClassNodeResolveException, ClassNode> getClassForNameWithoutPackage(String nameWithoutPackage);
}

View File

@ -0,0 +1,84 @@
package groowt.view.component.web.transpile.resolve;
import groowt.util.fp.either.Either;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ModuleNode;
import java.util.Objects;
public class ModuleNodeComponentClassNodeResolver extends CachingComponentClassNodeResolver {
private ModuleNode moduleNode;
public ModuleNodeComponentClassNodeResolver(WebViewComponentTemplateCompileUnit compileUnit) {
super(compileUnit);
}
public ModuleNode getModuleNode() {
return Objects.requireNonNull(this.moduleNode);
}
public void setModuleNode(ModuleNode moduleNode) {
this.moduleNode = Objects.requireNonNull(moduleNode);
}
@Override
public Either<ClassNodeResolveException, ClassNode> getClassForNameWithoutPackage(String nameWithoutPackage) {
return super.getClassForNameWithoutPackage(nameWithoutPackage).flatMapLeft(ignored -> {
// try regular imports first
final var importedClassNode = this.getModuleNode().getImportType(nameWithoutPackage);
if (importedClassNode != null) {
this.addClassNode(importedClassNode);
return Either.right(importedClassNode);
}
// try star imports
final var starImports = this.getModuleNode().getStarImports();
for (final var starImport : starImports) {
final var packageName = starImport.getPackageName();
final String fqn;
if (!packageName.equals(".") && packageName.endsWith(".")) {
fqn = packageName + nameWithoutPackage;
} else {
fqn = packageName + "." + nameWithoutPackage;
}
final var withPackage = this.getClassForFqn(fqn);
if (withPackage.isRight()) {
return withPackage;
}
}
// try pre-pending package and asking for fqn
final String moduleNodePackageName = this.getModuleNode().getPackageName();
final String packageName;
if (moduleNodePackageName != null) {
packageName = moduleNodePackageName;
} else {
packageName = "";
}
final String fqn;
if (packageName.equals(".") || packageName.isEmpty()) {
fqn = nameWithoutPackage;
} else if (packageName.endsWith(".")) {
fqn = packageName + nameWithoutPackage;
} else {
fqn = packageName + "." + nameWithoutPackage;
}
final var withPackage = this.getClassForFqn(fqn);
if (withPackage.isRight()) {
return withPackage;
} else {
return Either.left(new ClassNodeResolveException(
this.compileUnit,
nameWithoutPackage,
"Cannot resolve " + nameWithoutPackage
+ " from imports, package-local classes, or pre-added classes."
));
}
});
}
}

View File

@ -0,0 +1,31 @@
package groowt.view.component.web.transpile.resolve;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import java.util.regex.Pattern;
public final class ResolveUtil {
private static final Pattern packageSplitter = Pattern.compile("^(?<package>(?>\\p{Ll}[^.]*\\.)*)(?<top>\\p{Lu}[^.]*)(?<members>(?>\\.\\p{Lu}[^.]*)*)$");
public static ClassNode getClassNode(Class<?> clazz) {
return ClassHelper.makeCached(clazz);
}
public static String convertCanonicalNameToBinaryName(String canonicalName) {
final var matcher = packageSplitter.matcher(canonicalName);
if (matcher.matches()) {
return new StringBuilder()
.append(matcher.group("package"))
.append(matcher.group("top"))
.append(matcher.group("members").replaceAll("\\.", "\\$"))
.toString();
} else {
throw new IllegalArgumentException("Cannot split apart " + canonicalName);
}
}
private ResolveUtil() {}
}

View File

@ -1,16 +1,5 @@
package groowt.view.component.web.groovyc
import org.codehaus.groovy.control.CompilerConfiguration
import org.codehaus.groovy.control.customizers.ImportCustomizer
(configuration as CompilerConfiguration).tap {
pluginFactory = new WebViewComponentParserPluginFactory()
addCompilationCustomizers(new ImportCustomizer().tap {
addStarImports(
"groowt.view.component.web.lib",
"groowt.view.component.web.runtime",
"groowt.view.component.runtime"
)
})
}
(configuration as CompilerConfiguration).pluginFactory = new WebViewComponentParserPluginFactory()

View File

@ -3,6 +3,7 @@ package groowt.view.component.web.transpiler;
import groowt.view.component.web.compiler.WebViewComponentTemplateCompileUnit;
import groowt.view.component.web.transpile.SimpleTranspilerConfiguration;
import groowt.view.component.web.transpile.TranspilerConfiguration;
import groowt.view.component.web.transpile.resolve.CachingComponentClassNodeResolver;
import org.codehaus.groovy.ast.ModuleNode;
public class DefaultBodyTranspilerTests extends BodyTranspilerTests {
@ -12,7 +13,7 @@ public class DefaultBodyTranspilerTests extends BodyTranspilerTests {
WebViewComponentTemplateCompileUnit compileUnit,
ModuleNode moduleNode
) {
return SimpleTranspilerConfiguration.withDefaults();
return SimpleTranspilerConfiguration.withDefaults(new CachingComponentClassNodeResolver(compileUnit));
}
}

View File

@ -0,0 +1,25 @@
package groowt.view.component.web.transpiler;
import org.junit.jupiter.api.Test;
import static groowt.view.component.web.transpile.resolve.ResolveUtil.convertCanonicalNameToBinaryName;
import static org.junit.jupiter.api.Assertions.*;
public class ResolveUtilTests {
@Test
public void abcABC() {
assertEquals("a.b.c.A$B$C", convertCanonicalNameToBinaryName("a.b.c.A.B.C"));
}
@Test
public void ABC() {
assertEquals("A$B$C", convertCanonicalNameToBinaryName("A.B.C"));
}
@Test
public void abcA() {
assertEquals("a.b.c.A", convertCanonicalNameToBinaryName("a.b.c.A"));
}
}

View File

@ -1 +0,0 @@
<test test-attr />

View File

@ -1 +0,0 @@
<test-type />

View File

@ -1,7 +0,0 @@
0: ComponentOpen[1,1](<)
1: StringIdentifier[1,2](test)
2: Nlws[1,6]( )
3: AttributeIdentifier[1,7](test-attr)
4: Nlws[1,16]( )
5: ComponentSelfClose[1,17](/>)
6: RawText[1,19](\n)

View File

@ -1,5 +0,0 @@
0: ComponentOpen[1,1](<)
1: StringIdentifier[1,2](test-type)
2: Nlws[1,11]( )
3: ComponentSelfClose[1,12](/>)
4: RawText[1,14](\n)

View File

@ -2,6 +2,7 @@ package groowt.view.component.web.transpiler;
import groovy.lang.Tuple2;
import groowt.view.component.compiler.ComponentTemplateCompileException;
import groowt.view.component.compiler.DefaultComponentTemplateCompilerConfiguration;
import groowt.view.component.compiler.source.StringSource;
import groowt.view.component.web.BaseWebViewComponent;
import groowt.view.component.web.antlr.ParserUtil;
@ -54,6 +55,7 @@ public abstract class GroovyTranspilerTests {
final var cuNode = astBuilder.buildCompilationUnit(parseResult.getCompilationUnitContext());
try {
this.transpiler.transpile(
new DefaultComponentTemplateCompilerConfiguration(),
new DefaultWebViewComponentTemplateCompileUnit(
"<anonymous string source>",
AnonymousWebViewComponent.class,

View File

@ -1,10 +1,8 @@
#/usr/bin/env bash
VERSION="0.1.2"
if [ "\$1" == "--debug" ]; then
shift
gradle -q toolsJar && java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:8192 -cp build/libs/web-view-components-compiler-tools-\$VERSION.jar $mainClassName "\$@"
gradle -q toolsJar && java -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:8192 -cp build/libs/web-view-components-compiler-tools-0.1.0.jar $mainClassName "\$@"
else
gradle -q toolsJar && java -cp build/libs/web-view-components-compiler-tools-\$VERSION.jar $mainClassName "\$@"
gradle -q toolsJar && java -cp build/libs/web-view-components-compiler-tools-0.1.0.jar $mainClassName "\$@"
fi

View File

@ -9,7 +9,6 @@ import org.apache.logging.log4j.core.LoggerContext;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import picocli.CommandLine;
import picocli.CommandLine.Command;
@ -72,15 +71,6 @@ public final class GroovyWvcCompiler implements Callable<Integer> {
public Integer doCompile() {
final CompilerConfiguration configuration = new CompilerConfiguration();
final var addGroowtImports = new ImportCustomizer();
addGroowtImports.addStarImports(
"groowt.view.component.web.lib",
"groowt.view.component.web.runtime",
"groowt.view.component.runtime"
);
configuration.addCompilationCustomizers(addGroowtImports);
configuration.setPluginFactory(new WebViewComponentParserPluginFactory());
final CompilationUnit compilationUnit = new CompilationUnit(configuration);

View File

@ -133,10 +133,6 @@ java {
withSourcesJar()
}
jar {
archiveBaseName = 'groowt-web-view-components'
}
publishing {
publications {
create('webViewComponents', MavenPublication) {

View File

@ -14,8 +14,6 @@ class DefaultWebViewComponentScope extends DefaultComponentScope implements WebV
addWithAttr(DefaultCase)
addWithAttr(Each)
addWithAttr(Echo)
addWithAttr(Outlet)
addWithAttr(Render)
addWithAttr(Switch)
addWithAttr(WhenNotEmpty)
addWithAttr(WhenNotNull)

View File

@ -1,4 +1,4 @@
package groowt.view.component.web.lib
package groowt.view.component.web
trait WithHtml {

View File

@ -5,6 +5,7 @@ import groowt.view.component.ComponentRenderException
import groowt.view.component.context.ComponentContext
import groowt.view.component.context.ComponentScope.TypeAndFactory
import groowt.view.component.factory.ComponentFactory
import groowt.view.component.web.WithHtml
class IntrinsicHtml extends DelegatingWebViewComponent implements WithHtml {

View File

@ -1,28 +0,0 @@
package groowt.view.component.web.lib
import groowt.view.View
import groowt.view.component.ComponentRenderException
import groowt.view.component.runtime.DefaultComponentWriter
class Outlet extends DelegatingWebViewComponent {
private final List givenChildren
Outlet(Map attr) {
givenChildren = attr.children ?: []
}
@Override
protected View getDelegate() {
return { Writer w ->
if (!context.hasAncestor(OutletContainer)) {
throw new ComponentRenderException(
"<Outlet> is being used outside of a component implementing OutletContainer."
)
}
def cw = new DefaultComponentWriter(w, context.renderContext, context)
givenChildren.each { cw << it }
}
}
}

View File

@ -1,5 +0,0 @@
package groowt.view.component.web.lib
import groowt.view.component.web.WebViewComponent
interface OutletContainer extends WebViewComponent {}

View File

@ -1,28 +0,0 @@
package groowt.view.component.web.lib
import groowt.view.View
class Render extends DelegatingWebViewComponent {
private final Object item
Render(Map attr) {
item = Objects.requireNonNull(attr.item, "<Render> attribute 'item' must not be null.")
}
@Override
protected View getDelegate() {
return { Writer w ->
if (item.respondsTo('renderTo', [Writer] as Class[])) {
item.renderTo(w)
} else if (item.respondsTo('render')) {
w << item.render()
} else {
throw new IllegalArgumentException(
'<Render> must use an item which responds to either renderTo(Writer) or render().'
)
}
}
}
}

View File

@ -4,14 +4,13 @@ import groowt.view.component.ViewComponent;
import groowt.view.component.compiler.AbstractComponentTemplateCompileUnit;
import groowt.view.component.compiler.ComponentTemplateCompileException;
import groowt.view.component.compiler.ComponentTemplateCompileResult;
import groowt.view.component.compiler.ComponentTemplateCompilerConfiguration;
import groowt.view.component.compiler.source.ComponentTemplateSource;
import groowt.view.component.compiler.source.FileSource;
import groowt.view.component.compiler.source.URISource;
import groowt.view.component.compiler.source.URLSource;
import org.codehaus.groovy.control.CompilationUnit;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.Janitor;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.codehaus.groovy.control.io.ReaderSource;
import org.jetbrains.annotations.Nullable;
@ -21,20 +20,8 @@ import java.net.URI;
public class DefaultWebViewComponentTemplateCompileUnit extends AbstractComponentTemplateCompileUnit
implements ReaderSource, WebViewComponentTemplateCompileUnit {
private static CompilationUnit getCompilationUnit() {
final var configuration = new CompilerConfiguration();
final var addGroowtImports = new ImportCustomizer();
addGroowtImports.addStarImports(
"groowt.view.component.web.lib",
"groowt.view.component.web.runtime",
"groowt.view.component.runtime"
);
configuration.addCompilationCustomizers(addGroowtImports);
return new CompilationUnit(configuration);
}
private final String defaultPackageName;
private final CompilationUnit groovyCompilationUnit;
private final CompilationUnit groovyCompilationUnit = new CompilationUnit();
public DefaultWebViewComponentTemplateCompileUnit(
String descriptiveName,
@ -48,7 +35,6 @@ public class DefaultWebViewComponentTemplateCompileUnit extends AbstractComponen
} else {
this.defaultPackageName = defaultPackageName;
}
this.groovyCompilationUnit = getCompilationUnit();
}
@Override
@ -67,8 +53,9 @@ public class DefaultWebViewComponentTemplateCompileUnit extends AbstractComponen
}
@Override
public ComponentTemplateCompileResult compile() throws ComponentTemplateCompileException {
final WebViewComponentTemplateCompiler compiler = WebViewComponentTemplateCompiler.get();
public ComponentTemplateCompileResult compile(ComponentTemplateCompilerConfiguration configuration)
throws ComponentTemplateCompileException {
final WebViewComponentTemplateCompiler compiler = WebViewComponentTemplateCompiler.get(configuration);
return compiler.compile(this);
}

View File

@ -9,10 +9,10 @@ public interface WebViewComponentTemplateCompiler
extends ComponentTemplateCompiler<WebViewComponentTemplateCompileUnit> {
static WebViewComponentTemplateCompiler get() {
return get(new WebViewComponentTemplateCompilerConfiguration());
return get(new DefaultComponentTemplateCompilerConfiguration());
}
static WebViewComponentTemplateCompiler get(WebViewComponentTemplateCompilerConfiguration configuration) {
static WebViewComponentTemplateCompiler get(ComponentTemplateCompilerConfiguration configuration) {
final ServiceLoader<WebViewComponentTemplateCompilerFactory> factoryServiceLoader =
ServiceLoader.load(WebViewComponentTemplateCompilerFactory.class);
final var factory = factoryServiceLoader.findFirst()

View File

@ -1,17 +0,0 @@
package groowt.view.component.web.compiler;
import org.codehaus.groovy.control.CompilePhase;
public class WebViewComponentTemplateCompilerConfiguration {
private CompilePhase compilePhase = CompilePhase.CLASS_GENERATION;
public CompilePhase getCompilePhase() {
return this.compilePhase;
}
public void setCompilePhase(CompilePhase compilePhase) {
this.compilePhase = compilePhase;
}
}

View File

@ -1,5 +1,7 @@
package groowt.view.component.web.compiler;
import groowt.view.component.compiler.ComponentTemplateCompilerConfiguration;
public interface WebViewComponentTemplateCompilerFactory {
WebViewComponentTemplateCompiler create(WebViewComponentTemplateCompilerConfiguration configuration);
WebViewComponentTemplateCompiler create(ComponentTemplateCompilerConfiguration configuration);
}

View File

@ -1,35 +0,0 @@
package groowt.view.component.web.lib
import groowt.view.component.web.WebViewComponentContext
import groowt.view.component.web.WebViewComponentScope
import org.junit.jupiter.api.Test
class OutletTests extends AbstractWebViewComponentTests {
static final class DummyOutletContainer extends Echo implements OutletContainer {
DummyOutletContainer() {
super([:])
}
}
@Override
void configureContext(WebViewComponentContext context) {
context.configureRootScope(WebViewComponentScope) {
addWithNoArgConstructor(DummyOutletContainer)
}
}
@Test
void smokeScreen() {
doTest('<OutletTests.DummyOutletContainer><Outlet /></OutletTests.DummyOutletContainer>', '')
}
@Test
void withChildren() {
doTest('<OutletTests.DummyOutletContainer><Echo items={[0, 1, 2]}><Outlet children={items} /></Echo></OutletTests.DummyOutletContainer>', '012')
}
}

View File

@ -1,47 +0,0 @@
package groowt.view.component.web.lib
import org.junit.jupiter.api.Test
class RenderTests extends AbstractWebViewComponentTests {
static final class RenderToExample {
void renderTo(Writer w) {
w << 'Hello, World!'
}
}
static final class RenderExample {
String render() {
'Hello, World!'
}
}
@Test
void renderToExample() {
doTest('''
---
package groowt.view.component.web.lib
---
<Render item={new RenderTests.RenderToExample()} />
'''.stripIndent().trim(),
'Hello, World!'
)
}
@Test
void renderExample() {
doTest('''
---
package groowt.view.component.web.lib
---
<Render item={new RenderTests.RenderExample()} />
'''.stripIndent().trim(),
'Hello, World!'
)
}
}