diff --git a/cli/src/main/java/gyro/cli/Gyro.java b/cli/src/main/java/gyro/cli/Gyro.java index 9341d6d1d..70b5ac19d 100644 --- a/cli/src/main/java/gyro/cli/Gyro.java +++ b/cli/src/main/java/gyro/cli/Gyro.java @@ -19,6 +19,8 @@ import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -41,6 +43,7 @@ import gyro.core.command.VersionCommand; import gyro.core.scope.Defer; import gyro.core.scope.RootScope; +import gyro.core.ui.GyroUINotAvailableException; import gyro.core.validation.ValidationErrorException; import gyro.lang.Locatable; import gyro.lang.SyntaxError; @@ -71,23 +74,38 @@ public class Gyro { public static void main(String[] arguments) { ((Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)).setLevel(Level.OFF); - Gyro gyro = new Gyro(); GyroCore.pushUi(new CliGyroUI()); int exitStatus = 0; + boolean uiOverridden = false; try { - Optional.ofNullable(GyroCore.getRootDirectory()) - .map(d -> new RootScope(GyroCore.INIT_FILE, new LocalFileBackend(d), null, null)) - .ifPresent(r -> { - r.load(); - GyroCore.putStateBackends(r.getSettings(StateBackendSettings.class).getStateBackends()); - GyroCore.pushLockBackend(r.getSettings(LockBackendSettings.class).getLockBackend()); - }); + Path rootDirectory = GyroCore.getRootDirectory(); + if (rootDirectory != null) { + if (Files.exists(rootDirectory.resolve(GyroCore.UI_FILE).normalize())) { + new RootScope(GyroCore.UI_FILE, new LocalFileBackend(rootDirectory), null, null).load(); + uiOverridden = true; + } + + Optional.of(rootDirectory) + .map(d -> new RootScope(GyroCore.INIT_FILE, new LocalFileBackend(d), null, null)) + .ifPresent(r -> { + r.load(); + GyroCore.putStateBackends(r.getSettings(StateBackendSettings.class).getStateBackends()); + GyroCore.pushLockBackend(r.getSettings(LockBackendSettings.class).getLockBackend()); + }); + } + + Gyro gyro = new Gyro(); gyro.init(Arrays.asList(arguments)); gyro.run(); + } catch (GyroUINotAvailableException e) { + exitStatus = 4; + System.err.print("GyroCloudUI is not available.: "); + System.err.println(e.getMessage()); + } catch (Abort error) { exitStatus = 3; GyroCore.ui().write("\n@|red Aborted!|@\n\n"); @@ -99,6 +117,9 @@ public static void main(String[] arguments) { GyroCore.ui().write("\n"); } finally { + if (uiOverridden) { + GyroCore.popUi(); + } GyroCore.popUi(); GyroCore.popLockBackend(); System.exit(exitStatus); diff --git a/core/src/main/java/gyro/core/GyroCore.java b/core/src/main/java/gyro/core/GyroCore.java index 6891151a1..fe06e0b87 100644 --- a/core/src/main/java/gyro/core/GyroCore.java +++ b/core/src/main/java/gyro/core/GyroCore.java @@ -29,6 +29,8 @@ public class GyroCore { + public static final String UI_FILE = ".gyro/ui.gyro"; + public static final String INIT_FILE = ".gyro/init.gyro"; private static final ThreadLocalStack UI = new ThreadLocalStack<>(); diff --git a/core/src/main/java/gyro/core/scope/RootScope.java b/core/src/main/java/gyro/core/scope/RootScope.java index a2bada355..4d1ff44cc 100644 --- a/core/src/main/java/gyro/core/scope/RootScope.java +++ b/core/src/main/java/gyro/core/scope/RootScope.java @@ -88,6 +88,8 @@ import gyro.core.scope.converter.IdObjectToResource; import gyro.core.scope.converter.IterableToOne; import gyro.core.scope.converter.ResourceToIdObject; +import gyro.core.ui.UIDirectiveProcessor; +import gyro.core.ui.UIPlugin; import gyro.core.validation.ValidationError; import gyro.core.validation.ValidationErrorException; import gyro.core.virtual.VirtualDirectiveProcessor; @@ -171,7 +173,8 @@ public RootScope( new ModificationPlugin(), new ReferencePlugin(), new ResourcePlugin(), - new RootPlugin()) + new RootPlugin(), + new UIPlugin()) .forEach(p -> getSettings(PluginSettings.class).getPlugins().add(p)); Stream.of( @@ -200,6 +203,7 @@ public RootScope( RepositoryDirectiveProcessor.class, StateBackendDirectiveProcessor.class, TypeDescriptionDirectiveProcessor.class, + UIDirectiveProcessor.class, UpdateDirectiveProcessor.class, UsesCredentialsDirectiveProcessor.class, VirtualDirectiveProcessor.class, diff --git a/core/src/main/java/gyro/core/ui/GyroUINotAvailableException.java b/core/src/main/java/gyro/core/ui/GyroUINotAvailableException.java new file mode 100644 index 000000000..ecf78aad5 --- /dev/null +++ b/core/src/main/java/gyro/core/ui/GyroUINotAvailableException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2020, Perfect Sense, Inc. + * + * Licensed 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 gyro.core.ui; + +public class GyroUINotAvailableException extends RuntimeException { + + public GyroUINotAvailableException(String message) { + super(message); + } + + public GyroUINotAvailableException(String message, Throwable cause) { + super(message, cause); + } + + public GyroUINotAvailableException(Throwable cause) { + super(cause); + } +} diff --git a/core/src/main/java/gyro/core/ui/UIDirectiveProcessor.java b/core/src/main/java/gyro/core/ui/UIDirectiveProcessor.java new file mode 100644 index 000000000..e08881749 --- /dev/null +++ b/core/src/main/java/gyro/core/ui/UIDirectiveProcessor.java @@ -0,0 +1,67 @@ +/* + * Copyright 2020, Perfect Sense, Inc. + * + * Licensed 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 gyro.core.ui; + +import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; + +import com.google.common.base.CaseFormat; +import gyro.core.GyroCore; +import gyro.core.GyroException; +import gyro.core.GyroUI; +import gyro.core.Reflections; +import gyro.core.Type; +import gyro.core.directive.DirectiveProcessor; +import gyro.core.scope.RootScope; +import gyro.core.scope.Scope; +import gyro.lang.ast.block.DirectiveNode; + +@Type("ui") +public class UIDirectiveProcessor extends DirectiveProcessor { + + @Override + public void process(RootScope scope, DirectiveNode node) throws Exception { + validateArguments(node, 1, 1); + + String type = getArgument(scope, node, String.class, 0); + + UISettings settings = scope.getSettings(UISettings.class); + Class uiClass = settings.getUiClasses().get(type); + + if (uiClass == null) { + throw new GyroException( + "Make sure @|magenta '@plugin'|@ directive for a UI provider is placed before @|magenta '@ui'|@ directive in @|magenta '.gyro/ui.gyro'|@ file."); + } + + GyroUI ui = Reflections.newInstance(uiClass); + + Scope bodyScope = evaluateBody(scope, node); + + for (PropertyDescriptor property : Reflections.getBeanInfo(uiClass).getPropertyDescriptors()) { + Method setter = property.getWriteMethod(); + Object value = bodyScope.get(CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, property.getName())); + + if (setter != null && value != null) { + Reflections.invoke(setter, ui, scope.convertValue(setter.getGenericParameterTypes()[0], value)); + } + } + + if (!uiClass.isInstance(GyroCore.ui())) { + GyroCore.pushUi(ui); + } + } +} diff --git a/core/src/main/java/gyro/core/ui/UIPlugin.java b/core/src/main/java/gyro/core/ui/UIPlugin.java new file mode 100644 index 000000000..949f05d63 --- /dev/null +++ b/core/src/main/java/gyro/core/ui/UIPlugin.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020, Perfect Sense, Inc. + * + * Licensed 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 gyro.core.ui; + +import gyro.core.GyroUI; +import gyro.core.Reflections; +import gyro.core.plugin.Plugin; +import gyro.core.scope.RootScope; + +public class UIPlugin extends Plugin { + + @Override + public void onEachClass(RootScope root, Class aClass) throws Exception { + if (GyroUI.class.isAssignableFrom(aClass)) { + @SuppressWarnings("unchecked") + Class uiClass = (Class) aClass; + String namespace = Reflections.getNamespace(uiClass); + String type = Reflections.getTypeOptional(uiClass).orElse("ui"); + + root.getSettings(UISettings.class).getUiClasses().put(namespace + "::" + type, uiClass); + } + } +} diff --git a/core/src/main/java/gyro/core/ui/UISettings.java b/core/src/main/java/gyro/core/ui/UISettings.java new file mode 100644 index 000000000..06d77f4c5 --- /dev/null +++ b/core/src/main/java/gyro/core/ui/UISettings.java @@ -0,0 +1,39 @@ +/* + * Copyright 2020, Perfect Sense, Inc. + * + * Licensed 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 gyro.core.ui; + +import java.util.HashMap; +import java.util.Map; + +import gyro.core.GyroUI; +import gyro.core.scope.Settings; + +public class UISettings extends Settings { + + private Map> uiClasses; + + public Map> getUiClasses() { + if (uiClasses == null) { + uiClasses = new HashMap<>(); + } + return uiClasses; + } + + public void setUiClasses(Map> uiClasses) { + this.uiClasses = uiClasses; + } +}