CommanderUtils.java

package com.github.dakusui.actionunit.actions.cmd;

import com.github.dakusui.actionunit.actions.ContextVariable;
import com.github.dakusui.actionunit.actions.RetryOption;
import com.github.dakusui.actionunit.core.Action;
import com.github.dakusui.actionunit.core.Context;
import com.github.dakusui.actionunit.core.context.ContextFunctions;
import com.github.dakusui.actionunit.core.context.StreamGenerator;
import com.github.dakusui.actionunit.core.context.multiparams.Params;
import com.github.dakusui.processstreamer.core.process.ProcessStreamer;
import com.github.dakusui.processstreamer.core.process.Shell;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;

import static com.github.dakusui.actionunit.core.ActionSupport.leaf;
import static com.github.dakusui.actionunit.core.context.ContextFunctions.multiParamsConsumerFor;
import static com.github.dakusui.actionunit.core.context.ContextFunctions.multiParamsPredicateFor;
import static com.github.dakusui.actionunit.utils.InternalUtils.toStringIfOverriddenOrNoname;
import static com.github.dakusui.printables.PrintableFunctionals.printableConsumer;
import static com.github.dakusui.printables.PrintableFunctionals.printablePredicate;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.joining;

public enum CommanderUtils {
  ;

  private static final Logger LOGGER = LoggerFactory.getLogger(CommanderUtils.class);

  public static String quoteWithApostropheForShell(String s) {
    return String.format("'%s'", escapeSingleQuotesForShell(s));
  }

  private static String escapeSingleQuotesForShell(String s) {
    return requireNonNull(s).replaceAll("('+)", "'\"$1\"'");
  }

  static Action createAction(Commander<?> commander) {
    return RetryOption.retryAndTimeOut(
        leaf(createContextConsumer(commander)),
        commander.retryOption());
  }

  static Function<Context, Stream<String>> createStreamGenerator(
      Commander<?> commander) {
    return StreamGenerator.fromContextWith(
        new Function<Params, Stream<String>>() {
          @Override
          public Stream<String> apply(Params params) {
            return createProcessStreamerBuilder(commander, params)
                .checker(commander.checker())
                .build()
                .stream()
                .peek(commander.downstreamConsumer());
          }

          @Override
          public String toString() {
            return format("(%s)", commander.buildCommandLineComposer().format());
          }
        },
        commander.variables()
    );
  }

  static Consumer<Context> createContextConsumer(Commander<?> commander) {
    requireNonNull(commander);
    return multiParamsConsumerFor(commander.variables())
        .toContextConsumer(
            printableConsumer(
                (Params params) -> createProcessStreamerBuilder(commander, params)
                    .checker(commander.checker())
                    .build()
                    .stream()
                    .forEach(commander.downstreamConsumer()))
                .describe(() -> commander.buildCommandLineComposer().format()));
  }

  static Predicate<Context> createContextPredicate(Commander<?> commander) {
    return multiParamsPredicateFor(commander.variables())
        .toContextPredicate(printablePredicate(
            (Params params) -> {
              ProcessStreamer.Builder processStreamerBuilder = createProcessStreamerBuilder(commander, params);
              try {
                processStreamerBuilder
                    .checker(commander.checker())
                    .build()
                    .stream()
                    .forEach(commander.downstreamConsumer());
                return true;
              } catch (ProcessStreamer.Failure failure) {
                String msg = format("Condition '%s' was not satisfied: %s", commander.checker(), failure.getMessage());
                LOGGER.debug(msg);
                LOGGER.trace(msg, failure);
                return false;
              }
            })
            .describe(() -> format(
                "outputOf[command:'%s'].matches[%s]",
                commander.buildCommandLineComposer().format(),
                toStringIfOverriddenOrNoname(commander.checker()))));
  }

  static Function<Context, String> createContextFunction(Commander<?> commander) {
    return ContextFunctions.<String>multiParamsFunctionFor(commander.variables())
        .toContextFunction(params ->
            createProcessStreamerBuilder(commander, params)
                .checker(commander.checker())
                .build()
                .stream()
                .peek(commander.downstreamConsumer())
                .collect(joining(format("%n"))));
  }

  static ProcessStreamer.Builder createProcessStreamerBuilder(Commander<?> commander, Params params) {
    return createProcessStreamerBuilder(
        commander.stdin(),
        commander.shellManager().shellFor(commander.host()),
        commander.cwd().orElse(null),
        commander.envvars(),
        commander.buildCommandLineComposer(),
        params
    );
  }

  static ProcessStreamer.Builder createProcessStreamerBuilder(
      Stream<String> stdin,
      Shell shell,
      File cwd,
      Map<String, String> envvars,
      CommandLineComposer commandLineComposer,
      Params params) {
    ContextVariable[] variables = params.parameters().toArray(new ContextVariable[0]); // values
    Object[] variableValues = params.parameters()// values
        .stream()
        .map(params::valueOf)
        .toArray();
    String commandLine = commandLineComposer
        .apply(variables)
        .apply(params.context(), variableValues);
    LOGGER.info("Command Line:{}", commandLine);
    LOGGER.trace("Shell:{}", shell);
    if (cwd != null)
      LOGGER.debug("Cwd:{}", cwd);
    if (!envvars.isEmpty())
      LOGGER.debug("Environment variables:{}", envvars);
    ProcessStreamer.Builder ret;
    if (stdin == null)
      ret = ProcessStreamer.source(shell);
    else
      ret = ProcessStreamer.pipe(stdin, shell);
    envvars.forEach(ret::env);
    if (cwd != null)
      ret.cwd(cwd);
    return ret.command(commandLine);
  }
}