diff --git a/README.adoc b/README.adoc
index 81e15d4f..bd8cfdc1 100644
--- a/README.adoc
+++ b/README.adoc
@@ -1371,3 +1371,50 @@ Just set extension property `tomitribe.crest.useInPlaceRegistrations` to `true`:
----
This enables to use the same scanning for both tasks and therefore to have a common and unified scanning for java and native runs.
+
+== Run commands from source
+
+There is an experimental mode in crest enabling you to write commands in a standalone `.java` and run it with `tomitribe-crest.jar` fatjar directly.
+The trick is to pass as first parameter `--crest.source=/path/to/file/with/commands.java`.
+
+[source,java]
+.commands.java
+----
+import org.tomitribe.crest.api.Command;
+import java.time.LocalDate;
+
+class Command1 {
+ @Command
+ String ok() {
+ return "ok";
+ }
+}
+
+@Command
+class Command2 {
+ @Command
+ String time() {
+ return LocalDate.now().toString();
+ }
+}
+----
+
+IMPORTANT: this mode has some limitation like not supporting (yet) a dynamic classpath so it just uses the script and the launching classpath.
+
+TIP: to ease writing scripts with completion the lines starting with `//-- ` will drop this prefix.
+It avoids compilations errors in general on classes no in the classpath (crest there).
+
+Usage example:
+
+[source,bash]
+----
+java \
+ -jar tomitribe-crest-0.20-fatjar.jar \
+ --crest.source=mycommands.java \
+ my-command \
+ --foo=bar
+----
+
+IMPORTANT: this requires to run with a *JDK* to work.
+
+TIP: you can also use it in a script easily.
diff --git a/tomitribe-crest-arthur-extension/src/main/java/org/tomitribe/crest/arthur/svm/MainSubstitute.java b/tomitribe-crest-arthur-extension/src/main/java/org/tomitribe/crest/arthur/svm/MainSubstitute.java
new file mode 100644
index 00000000..e5c75a86
--- /dev/null
+++ b/tomitribe-crest-arthur-extension/src/main/java/org/tomitribe/crest/arthur/svm/MainSubstitute.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.tomitribe.crest.arthur.svm;
+
+import com.oracle.svm.core.annotate.Substitute;
+import com.oracle.svm.core.annotate.TargetClass;
+import org.tomitribe.crest.Main;
+
+@TargetClass(Main.class)
+public final class MainSubstitute {
+ @Substitute
+ private void handleSource(final String command) {
+ throw new UnsupportedOperationException("In native mode, compiler feature is disabled");
+ }
+}
diff --git a/tomitribe-crest-arthur-extension/src/main/java/org/tomitribe/crest/arthur/svm/SimpleCompilerDelete.java b/tomitribe-crest-arthur-extension/src/main/java/org/tomitribe/crest/arthur/svm/SimpleCompilerDelete.java
new file mode 100644
index 00000000..f5ce1aa0
--- /dev/null
+++ b/tomitribe-crest-arthur-extension/src/main/java/org/tomitribe/crest/arthur/svm/SimpleCompilerDelete.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.tomitribe.crest.arthur.svm;
+
+import com.oracle.svm.core.annotate.Delete;
+import com.oracle.svm.core.annotate.TargetClass;
+import org.tomitribe.crest.compiler.SimpleCompiler;
+
+@Delete("native mode will not support compilation on the fly")
+@TargetClass(SimpleCompiler.class)
+public final class SimpleCompilerDelete {
+}
diff --git a/tomitribe-crest/pom.xml b/tomitribe-crest/pom.xml
index 6df01930..2399b9f0 100644
--- a/tomitribe-crest/pom.xml
+++ b/tomitribe-crest/pom.xml
@@ -81,16 +81,22 @@
${project.version}
test
-
- com.google.auto.service
- auto-service
- 1.0-rc2
- true
-
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ default-compile
+
+ none
+
+
+
+
org.apache.maven.plugins
maven-surefire-plugin
@@ -103,6 +109,42 @@
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 3.3.0
+
+
+ package
+
+ shade
+
+
+ true
+ fatjar
+
+
+ org.tomitribe.crest.Main
+
+
+
+
+ org.apache.xbean:*
+
+
+
+
+ *:*
+
+ META-INF/LICENSE.txt
+ META-INF/NOTICE.txt
+
+
+
+
+
+
+
diff --git a/tomitribe-crest/src/main/java/org/tomitribe/crest/Main.java b/tomitribe-crest/src/main/java/org/tomitribe/crest/Main.java
index 664ec1ac..9b13ab30 100644
--- a/tomitribe-crest/src/main/java/org/tomitribe/crest/Main.java
+++ b/tomitribe-crest/src/main/java/org/tomitribe/crest/Main.java
@@ -26,10 +26,12 @@
import org.tomitribe.crest.cmds.HelpPrintedException;
import org.tomitribe.crest.cmds.processors.Commands;
import org.tomitribe.crest.cmds.processors.Help;
+import org.tomitribe.crest.compiler.SimpleCompiler;
import org.tomitribe.crest.contexts.DefaultsContext;
import org.tomitribe.crest.contexts.SystemPropertiesDefaultsContext;
import org.tomitribe.crest.environments.Environment;
import org.tomitribe.crest.environments.SystemEnvironment;
+import org.tomitribe.crest.interceptor.InterceptorAnnotationNotFoundException;
import org.tomitribe.crest.interceptor.internal.InternalInterceptor;
import org.tomitribe.crest.table.Formatting;
import org.tomitribe.crest.table.TableInterceptor;
@@ -50,10 +52,14 @@
import java.util.function.Consumer;
import java.util.stream.Stream;
-public class Main implements Completer {
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
- protected final Map commands = new ConcurrentHashMap<>();
- protected final Map, InternalInterceptor> interceptors = new HashMap<>();
+public class Main implements Completer {
+ // this is "exposed" so we don't type them as ContextualizableMap for children
+ protected final Map commands = new ContextualizableMap<>();
+ protected final Map, InternalInterceptor> interceptors = new ContextualizableMap<>();
+ protected final DefaultsContext defaultsContext;
public Main() {
this(new SystemPropertiesDefaultsContext(), Commands.load());
@@ -68,6 +74,8 @@ public Main(final DefaultsContext defaultsContext, final Class>... classes) {
}
public Main(final DefaultsContext defaultsContext, final Iterable> classes) {
+ this.defaultsContext = defaultsContext;
+
for (final Class clazz : classes) {
processClass(defaultsContext, clazz);
}
@@ -84,29 +92,14 @@ public void processClass(final DefaultsContext defaultsContext, final Class> c
if (!m.isEmpty()) {
this.commands.putAll(m);
} else {
-
- final InternalInterceptor internalInterceptor = InternalInterceptor.from(clazz);
- if (interceptors.put(clazz, internalInterceptor) != null) {
- throw new IllegalArgumentException(clazz + " interceptor is conflicting");
- }
-
- for (final Annotation annotation : clazz.getDeclaredAnnotations()) {
- if (isCustomInterceptorAnnotation(annotation)) {
- if (interceptors.put(annotation.annotationType(), internalInterceptor) != null) {
- throw new IllegalArgumentException(clazz + " interceptor is conflicting");
- }
- }
- }
+ storeInterceptors(interceptors, clazz);
}
}
private static boolean isCustomInterceptorAnnotation(final Annotation annotation) {
- for (final Annotation declaredAnnotation : annotation.annotationType().getDeclaredAnnotations()) {
- if (declaredAnnotation instanceof CrestInterceptor) {
- return true;
- }
- }
- return false;
+ return Stream.of(annotation.annotationType().getDeclaredAnnotations())
+ .map(Annotation::annotationType)
+ .anyMatch(it -> it == CrestInterceptor.class);
}
public Main(final Iterable> classes) {
@@ -238,25 +231,118 @@ public void main(final Environment env, final String... args) throws Exception {
public Object exec(String... args) throws Exception {
final List list = processSystemProperties(args);
- final String command = (list.isEmpty()) ? "help" : list.remove(0);
- args = list.toArray(new String[list.size()]);
-
+ String command = (list.isEmpty()) ? "help" : list.remove(0);
if (command.equals("_completion")) {
return BashCompletion.generate(this, args);
}
- final Cmd cmd = commands.get(command);
+ final Runnable cleanup;
+ if (command.startsWith("--crest.source=")) {
+ // todo: support multiple? for now all commands can be in a single file
+ cleanup = handleSource(command);
+ command = (list.isEmpty()) ? "help" : list.remove(0);
+ args = list.toArray(new String[0]);
+ } else {
+ args = list.toArray(new String[0]);
+ cleanup = null;
+ }
+
+ try {
+ final Cmd cmd = commands.get(command);
+
+ if (cmd == null) {
+
+ final PrintStream err = Environment.ENVIRONMENT_THREAD_LOCAL.get().getError();
+ err.println("Unknown command: " + command);
+ err.println();
+ commands.get("help").exec(interceptors);
+ throw new IllegalArgumentException();
+ }
+
+ return cmd.exec(interceptors, args);
+ } finally {
+ if (cleanup != null) {
+ cleanup.run();
+ }
+ }
+ }
+
+ // DON'T MODIFY WITHOUT FIXING org.tomitribe.crest.arthur.svm.DropCompilerSupport
+ private Runnable handleSource(final String command) {
+ final List> additionalClasses = SimpleCompiler
+ .compile(command.substring("--crest.source=".length()))
+ .ownedClasses()
+ .collect(toList());
+
+ final Map tempCommands = new HashMap<>();
+ final Map, InternalInterceptor> tempInterceptors = new HashMap<>();
+ final ThreadLocal