Commander.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.StreamGenerator;
import com.github.dakusui.actionunit.exceptions.ActionException;
import com.github.dakusui.processstreamer.core.process.ProcessStreamer.Checker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.*;
import java.util.stream.Stream;
import static com.github.dakusui.actionunit.core.ActionSupport.named;
import static java.lang.String.format;
import static java.util.Collections.emptyMap;
import static java.util.Objects.requireNonNull;
/**
* A base builder class to construct an action that runs a command line program.
*
* @param <C> A class refers to itself.
* @see com.github.dakusui.actionunit.actions.cmd.unix.Cmd
*/
public abstract class Commander<C extends Commander<C>> implements Cloneable {
private static final Logger LOGGER = LoggerFactory.getLogger(Commander.class);
CommandLineComposer.Builder commandLineComposerBuilder;
private final Function<ContextVariable[], IntFunction<String>> parameterPlaceHolderFactory;
private RetryOption retryOption;
private Supplier<Consumer<String>> downstreamConsumerFactory;
private Supplier<Checker> checkerFactory;
private Stream<String> stdin;
private ShellManager shellManager;
private File cwd = null;
private final Map<String, String> envvars;
private String description = null;
private String host;
protected Commander(CommanderConfig config, String commandName) {
this.parameterPlaceHolderFactory = config.variablePlaceHolderFormatter();
this.envvars = new LinkedHashMap<>();
this.stdin(Stream.empty())
.retryOption(config.retryOption())
.shellManager(config.shellManager())
.host("localhost")
.checker(config.checker())
.downstreamConsumer(LOGGER::trace);
this.commandName(resolveCommandName(config, commandName));
}
String resolveCommandName(CommanderConfig config, String commandName) {
return config.programNameResolver().apply(this.host(), commandName);
}
@SuppressWarnings("unchecked")
public C describe(String description) {
this.description = description;
return (C) this;
}
@SuppressWarnings("unchecked")
public C host(String host) {
this.host = requireNonNull(host);
return (C) this;
}
@SuppressWarnings("unchecked")
public C clone() {
try {
C ret = (C) super.clone();
if (ret.commandLineComposerBuilder().isPresent())
ret.commandLineComposerBuilder = ret.commandLineComposerBuilderIfSet().clone();
return ret;
} catch (CloneNotSupportedException e) {
throw ActionException.wrap(e);
}
}
@SuppressWarnings("unchecked")
public C retryOption(RetryOption retryOption) {
this.retryOption = retryOption;
return (C) this;
}
/**
* Sets a down-stream consumer to this builder object.
* In case the down-stream consumer is stateful, use {@link Commander#downstreamConsumerFactory(Supplier)}
* method instead and give a supplier that returns a new consumer object every time
* when its {@code get()} method is called.
*
* @param downstreamConsumer A down-stream consumer.
* @return This object
*/
public C downstreamConsumer(Consumer<String> downstreamConsumer) {
requireNonNull(downstreamConsumer);
return this.downstreamConsumerFactory(() -> downstreamConsumer);
}
/**
* Sets a downstream consumer's factory to this builder object.
*
* @param downstreamConsumerFactory A supplier of a down-stream consumer.
* @return This object
*/
@SuppressWarnings("unchecked")
public C downstreamConsumerFactory(Supplier<Consumer<String>> downstreamConsumerFactory) {
this.downstreamConsumerFactory = requireNonNull(downstreamConsumerFactory);
return (C) this;
}
/**
* Sets a checker to this builder object.
* In case the checker is stateful, use {@link Commander#checkerFactory(Supplier)}
* method instead and give a suuplier that returns a new checker object every
* time when its {@code get()} method is called.
*
* @param checker A checker object
* @return This object
*/
public C checker(Checker checker) {
requireNonNull(checker);
return this.checkerFactory(() -> checker);
}
@SuppressWarnings("unchecked")
public C checkerFactory(Supplier<Checker> checkerFactory) {
this.checkerFactory = requireNonNull(checkerFactory);
return (C) this;
}
@SuppressWarnings("unchecked")
public C stdin(Stream<String> stdin) {
this.stdin = requireNonNull(stdin);
return (C) this;
}
@SuppressWarnings("unchecked")
public C shellManager(ShellManager shellManager) {
this.shellManager = requireNonNull(shellManager);
return (C) this;
}
@SuppressWarnings("unchecked")
public C cwd(File cwd) {
this.cwd = requireNonNull(cwd);
return (C) this;
}
@SuppressWarnings("unchecked")
public C setenv(String varname, String varvalue) {
this.envvars.put(requireNonNull(varname), requireNonNull(varvalue));
return (C) this;
}
/**
* A building method that returns an {@link Action} object.
*
* @return An action object.
*/
public Action toAction() {
Action action = CommanderUtils.createAction(this);
return this.description != null ?
named(this.description, action) :
action;
}
public Action build() {
return toAction();
}
public Action $() {
return build();
}
/**
* A building method that returns {@link StreamGenerator} object.
*
* @return A stream generator object.
*/
public Function<Context, Stream<String>> toStreamGenerator() {
return CommanderUtils.createStreamGenerator(this);
}
public Consumer<Context> toContextConsumer() {
return CommanderUtils.createContextConsumer(this);
}
public Predicate<Context> toContextPredicate() {
return CommanderUtils.createContextPredicate(this);
}
public Function<Context, String> toContextFunction() {
return CommanderUtils.createContextFunction(this);
}
/**
* A short-hand method to execute a command directly.
*
* @return data stream from the executed command.
*/
public Stream<String> run() {
return run(emptyMap());
}
/**
* A short-hand method to execute a command directly.
*
* @param variables Variables to be set to the context.
* @return data stream from the executed command.
*/
public Stream<String> run(Map<String, Object> variables) {
Context context = Context.create();
requireNonNull(variables).forEach(context::assignTo);
return toStreamGenerator().apply(context);
}
/**
* Adds a {@code target} to this object. A target may be a file, directory, or a
* message. For instance, for a {@code cat} command, which usually processes
* a file or files, this method will be used to add a file to be processed by
* the command.
* The value of the {@code target} parameter will be quoted.
*
* @param target A target string
* @return This object
*/
public C add(Function<Context, String> target) {
return this.append(" ").appendq(requireNonNull(target));
}
/**
* Adds a {@code target} to this object. A target may be a file, directory, or a
* message. For instance, for a {@code cat} command, which usually processes
* a file or files, this method will be used to add a file to be processed by
* the command.
* The value of the {@code target} parameter will be quoted.
*
* @param target A target string
* @return This object
*/
public C add(String target) {
return this.append(" ").appendq(requireNonNull(target));
}
public C append(Function<Context, String> func) {
return append(func, false);
}
public C appendq(Function<Context, String> func) {
return append(func, true);
}
@SuppressWarnings("unchecked")
public C append(Function<Context, String> func, boolean b) {
commandLineComposerBuilderIfSet().append(func, b);
return (C) this;
}
public C append(String text) {
return append(text, false);
}
public C appendq(String text) {
return append(text, true);
}
@SuppressWarnings("unchecked")
public C append(String text, boolean b) {
commandLineComposerBuilderIfSet().append(text, b);
return (C) this;
}
public C addOption(String option) {
return this.append(" ").append(option);
}
public C appendVariable(ContextVariable variableName) {
return appendVariable(variableName, false);
}
public C appendQuotedVariable(ContextVariable variableName) {
return appendVariable(variableName, true);
}
@SuppressWarnings("unchecked")
public C appendVariable(ContextVariable variableName, boolean b) {
commandLineComposerBuilderIfSet().appendVariable(variableName, b);
return (C) this;
}
@SuppressWarnings("unchecked")
public C declareVariable(ContextVariable variableName) {
this.commandLineComposerBuilderIfSet().declareVariable(variableName);
return (C) this;
}
public RetryOption retryOption() {
return this.retryOption;
}
public Supplier<Checker> checkerFactory() {
return this.checkerFactory;
}
public Supplier<Consumer<String>> downstreamConsumerFactory() {
return this.downstreamConsumerFactory;
}
public Stream<String> stdin() {
return this.stdin;
}
public ShellManager shellManager() {
return this.shellManager;
}
public Map<String, String> envvars() {
return Collections.unmodifiableMap(this.envvars);
}
public Optional<File> cwd() {
return Optional.ofNullable(this.cwd);
}
@Override
public String toString() {
return format(
"%s:Shell:(%s), CommandLine(%s)",
this.getClass().getSimpleName(),
shellManager(),
commandLineComposerBuilder);
}
@SuppressWarnings("unchecked")
public C commandName(String command) {
this.commandLineComposerBuilder = new CommandLineComposer.Builder(this.parameterPlaceHolderFactory())
.append(command, false);
return (C) this;
}
protected CommandLineComposer.Builder commandLineComposerBuilderIfSet() {
return this.commandLineComposerBuilder().orElseThrow(IllegalStateException::new);
}
public CommandLineComposer buildCommandLineComposer() {
return commandLineComposerBuilderIfSet().clone().build();
}
Checker checker() {
return this.checkerFactory.get();
}
Consumer<String> downstreamConsumer() {
return this.downstreamConsumerFactory.get();
}
ContextVariable[] variables() {
return commandLineComposerBuilderIfSet().knownVariables();
}
Optional<CommandLineComposer.Builder> commandLineComposerBuilder() {
return Optional.ofNullable(commandLineComposerBuilder);
}
private Function<ContextVariable[], IntFunction<String>> parameterPlaceHolderFactory() {
return this.parameterPlaceHolderFactory;
}
public String host() {
return this.host;
}
}