001/*
002 * Copyright (c) 2012-2021 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others
003 *
004 * This program and the accompanying materials are made available under the
005 * terms of the Eclipse Public License 2.0 which is available at
006 * http://www.eclipse.org/legal/epl-2.0.
007 *
008 * SPDX-License-Identifier: EPL-2.0
009 */
010
011package org.eclipse.golo.cli.command.spi;
012
013import org.eclipse.golo.compiler.GoloCompilationException;
014import gololang.Messages;
015import gololang.ir.GoloModule;
016
017import java.util.Comparator;
018import java.util.Set;
019import java.util.function.Consumer;
020import java.util.function.Function;
021import java.lang.invoke.MethodHandle;
022import java.io.File;
023
024import static java.lang.invoke.MethodHandles.publicLookup;
025import static java.lang.invoke.MethodType.methodType;
026
027import static gololang.Messages.*;
028
029
030public interface CliCommand {
031
032  Comparator<GoloModule> MODULE_COMPARATOR = (GoloModule m1, GoloModule m2) -> {
033    if (m1 == null && m2 != null) { return -1; }
034    if (m1 != null && m2 == null) { return 1; }
035    if (m1 == null && m2 == null) { return 0; }
036    if (m1.hasMacros() && !m2.hasMacros()) { return -1; }
037    if (!m1.hasMacros() && m2.hasMacros()) { return 1; }
038    Set<String> m1Used = m1.getUsedModules();
039    Set<String> m2Used = m2.getUsedModules();
040    if (m1Used.contains(m2.getPackageAndClass().toString())) { return 1; }
041    if (m2Used.contains(m1.getPackageAndClass().toString())) { return -1; }
042    if (m1.getImports().stream().anyMatch((mi) -> mi.getPackageAndClass().equals(m2.getPackageAndClass()))) { return 1; }
043    if (m2.getImports().stream().anyMatch((mi) -> mi.getPackageAndClass().equals(m1.getPackageAndClass()))) { return -1; }
044    if (m1.hasMain() && !m2.hasMain()) { return 1; }
045    if (m2.hasMain() && !m1.hasMain()) { return -1; }
046    return 0;
047  };
048
049  void execute() throws Throwable;
050
051  default void callRun(Class<?> klass, String[] arguments) throws Throwable {
052    MethodHandle main;
053    try {
054      main = publicLookup().findStatic(klass, "main", methodType(void.class, String[].class));
055    } catch (NoSuchMethodException e) {
056      throw new NoMainMethodException().initCause(e);
057    }
058    main.invoke(arguments);
059  }
060
061  default boolean canRead(File source) {
062    if (source == null) { return false; }
063    if (!source.canRead()) {
064      warning(message("file_not_found", source.getPath()));
065      return false;
066    }
067    return true;
068  }
069
070  default Consumer<File> wrappedAction(boolean exitOnError, GolofileAction action) {
071    return source -> {
072      if (canRead(source)) {
073        try {
074          action.accept(source);
075        } catch (GoloCompilationException e) {
076          handleCompilationException(e, exitOnError);
077        } catch (Throwable e) {
078          handleThrowable(e, exitOnError);
079        }
080      }
081    };
082  }
083
084  default Consumer<File> wrappedAction(GolofileAction action) {
085    return wrappedAction(false, action);
086  }
087
088  default <T, R> Function<T, R> wrappedTreatment(GoloCompilationTreatment<T, R> t) {
089    return data -> {
090      if (data == null) {
091        return null;
092      }
093      try {
094        return t.apply(data);
095      } catch (GoloCompilationException e) {
096        handleCompilationException(e, false);
097        return null;
098      } catch (Throwable e) {
099        handleThrowable(e, false);
100        return null;
101      }
102    };
103  }
104
105  default <T> Function<T, T> displayInfo(String message) {
106    if (this.verbose()) {
107      return object -> {
108        if (object != null) {
109          info(String.format(message, object));
110        }
111        return object;
112      };
113    }
114    return Function.identity();
115  }
116
117  default boolean verbose() {
118    return false;
119  }
120
121  default void handleCompilationException(GoloCompilationException e) {
122    handleCompilationException(e, true);
123  }
124
125  default void handleCompilationException(GoloCompilationException e, boolean exit) {
126    Messages.error(e.getLocalizedMessage());
127    for (GoloCompilationException.Problem problem : e.getProblems()) {
128      Messages.error(problem.getDescription(), "  ");
129      Throwable cause = problem.getCause();
130      if (cause != null) {
131        handleThrowable(cause, false, gololang.Runtime.debugMode(), "    ");
132      }
133    }
134    if (exit) {
135      System.exit(1);
136    }
137  }
138
139  default void handleThrowable(Throwable e) {
140    handleThrowable(e, gololang.Runtime.showStackTrace());
141  }
142
143  default void handleThrowable(Throwable e, boolean exit) {
144    handleThrowable(e, exit, gololang.Runtime.debugMode() || gololang.Runtime.showStackTrace());
145  }
146
147  default void handleThrowable(Throwable e, boolean exit, boolean withStack) {
148    handleThrowable(e, exit, withStack, "");
149  }
150
151  default void handleThrowable(Throwable e, boolean exit, boolean withStack, String indent) {
152    Messages.error(e.getLocalizedMessage(), indent);
153    if (e.getCause() != null) {
154      Messages.error(e.getCause().getLocalizedMessage(), "  " + indent);
155    }
156    if (withStack) {
157      Messages.printStackTrace(e);
158    } else {
159      Messages.error(Messages.message("use_debug"));
160    }
161    if (exit) {
162      System.exit(1);
163    }
164  }
165
166  class NoMainMethodException extends NoSuchMethodException { }
167
168  @FunctionalInterface
169  interface GolofileAction {
170    void accept(File source) throws Throwable;
171  }
172
173  @FunctionalInterface
174  interface GoloCompilationTreatment<T, R> {
175    R apply(T o) throws Throwable;
176  }
177
178}