Skip to content

Commit

Permalink
chore: Partial implementation updates
Browse files Browse the repository at this point in the history
  • Loading branch information
rsenden committed Sep 23, 2024
1 parent e3c59d3 commit 2d74955
Show file tree
Hide file tree
Showing 13 changed files with 331 additions and 160 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Copyright 2023 Open Text.
*
* The only warranties for products and services of Open Text
* and its affiliates and licensors ("Open Text") are as may
* be set forth in the express warranty statements accompanying
* such products and services. Nothing herein should be construed
* as constituting an additional warranty. Open Text shall not be
* liable for technical or editorial errors or omissions contained
* herein. The information contained herein is subject to change
* without notice.
*/
package com.fortify.cli.common.action.cli.cmd;

import com.fortify.cli.common.action.cli.mixin.ActionResolverMixin;
import com.fortify.cli.common.action.helper.ActionLoaderHelper.ActionValidationHandler;
import com.fortify.cli.common.action.runner.n.ActionCommandLineFactory;
import com.fortify.cli.common.action.runner.n.ActionRuntimeConfig;
import com.fortify.cli.common.action.runner.n.ActionSourceConfig;
import com.fortify.cli.common.cli.cmd.AbstractRunnableCommand;
import com.fortify.cli.common.cli.mixin.CommandHelperMixin;

import picocli.CommandLine;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Unmatched;

public abstract class AbstractActionNewHelpCommand extends AbstractRunnableCommand {
@Mixin private ActionResolverMixin.RequiredParameter actionResolver;
@Mixin private CommandHelperMixin commandHelper;
// We explicitly ignore any unknown CLI args, to allow for easy switching between run and help commands.
@Unmatched private String[] actionArgs;

@Override
public Integer call() throws Exception {
initMixins();
createCommandLine().usage(System.out);
return 0;
}

private final CommandLine createCommandLine() {
return ActionCommandLineFactory.builder()
.actionSourceConfig(new ActionSourceConfig(actionResolver, actionResolver.loadAction(getType(), ActionValidationHandler.WARN)))
.actionRuntimeConfig(createActionRuntimeConfig())
.build()
.createCommandLine();
}

protected abstract String getType();
protected abstract ActionRuntimeConfig createActionRuntimeConfig();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Copyright 2023 Open Text.
*
* The only warranties for products and services of Open Text
* and its affiliates and licensors ("Open Text") are as may
* be set forth in the express warranty statements accompanying
* such products and services. Nothing herein should be construed
* as constituting an additional warranty. Open Text shall not be
* liable for technical or editorial errors or omissions contained
* herein. The information contained herein is subject to change
* without notice.
*/
package com.fortify.cli.common.action.cli.cmd;

import com.fortify.cli.common.action.cli.mixin.ActionResolverMixin;
import com.fortify.cli.common.action.helper.ActionLoaderHelper.ActionValidationHandler;
import com.fortify.cli.common.action.runner.n.ActionCommandLineFactory;
import com.fortify.cli.common.action.runner.n.ActionRuntimeConfig;
import com.fortify.cli.common.action.runner.n.ActionSourceConfig;
import com.fortify.cli.common.cli.cmd.AbstractRunnableCommand;
import com.fortify.cli.common.cli.mixin.CommandHelperMixin;

import picocli.CommandLine;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Unmatched;

public abstract class AbstractActionNewRunCommand extends AbstractRunnableCommand {
@Mixin private ActionResolverMixin.RequiredParameter actionResolver;
@Mixin private CommandHelperMixin commandHelper;
@Unmatched private String[] actionArgs;

@Override
public Integer call() throws Exception {
initMixins();
// TODO Handle progress writer, delayed console writers, ...
createCommandLine().execute(actionArgs==null ? new String[] {} : actionArgs);
return 0;
}

private final CommandLine createCommandLine() {
return ActionCommandLineFactory.builder()
.actionSourceConfig(new ActionSourceConfig(actionResolver, actionResolver.loadAction(getType(), ActionValidationHandler.WARN)))
.actionRuntimeConfig(createActionRuntimeConfig())
.build()
.createCommandLine();
}

protected abstract String getType();
protected abstract ActionRuntimeConfig createActionRuntimeConfig();
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*******************************************************************************/
package com.fortify.cli.common.action.cli.mixin;

import org.apache.commons.lang3.StringUtils;

import com.fortify.cli.common.action.helper.ActionLoaderHelper;
import com.fortify.cli.common.action.helper.ActionLoaderHelper.ActionLoadResult;
import com.fortify.cli.common.action.helper.ActionLoaderHelper.ActionValidationHandler;
Expand Down Expand Up @@ -44,6 +46,20 @@ public Action loadAction(String type, ActionValidationHandler actionValidationHa
public String loadActionContents(String type, ActionValidationHandler actionValidationHandler) {
return load(type, actionValidationHandler).getActionText();
}

public final String asArgsString() {
StringBuilder sb = new StringBuilder();
sb.append(getAction());
var actionSourceResolverArgsString = actionSourceResolver==null ? null : actionSourceResolver.asArgsString();
if ( StringUtils.isNotBlank(actionSourceResolverArgsString) ) {
sb.append(" ").append(actionSourceResolverArgsString);
}
var publicKeyResolverArgsString = publicKeyResolver==null ? null : publicKeyResolver.asArgsString();
if ( StringUtils.isNotBlank(publicKeyResolverArgsString) ) {
sb.append(" ").append(publicKeyResolverArgsString);
}
return sb.toString();
}
}

public static class RequiredParameter extends AbstractActionResolverMixin {
Expand All @@ -56,5 +72,9 @@ public static class OptionalParameter extends AbstractActionResolverMixin {

private static class PublicKeyResolverMixin extends AbstractTextResolverMixin {
@Getter @Option(names={"--pubkey"}, required = false, descriptionKey = "fcli.action.resolver.pubkey", paramLabel = "source") private String textSource;

public String asArgsString() {
return StringUtils.isBlank(textSource) ? null : ("--pubkey "+textSource);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ public List<ActionSource> getActionSources(String type) {
? ActionSource.defaultActionSources(type)
: ActionSource.externalActionSources(source);
}

public final String asArgsString() {
var source = getSource();
return StringUtils.isBlank(source) ? null : ("--from-zip "+source);
}
}

public static class OptionalOption extends AbstractActionSourceResolverMixin {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,14 @@
* herein. The information contained herein is subject to change
* without notice.
*/
package com.fortify.cli.common.action.helper;
package com.fortify.cli.common.action.runner.n;

import java.util.concurrent.Callable;
import java.util.stream.StreamSupport;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fortify.cli.common.action.model.Action;
import com.fortify.cli.common.action.runner.ActionRunnerCommand;
import com.fortify.cli.common.cli.util.FortifyCLIDefaultValueProvider;
import com.fortify.cli.common.crypto.helper.SignatureHelper.PublicKeyDescriptor;
import com.fortify.cli.common.crypto.helper.SignatureHelper.SignatureMetadata;
Expand All @@ -33,14 +32,8 @@

@Builder
public class ActionCommandLineFactory {
/** Action run command for which to generate a CommandLine instance */
private final String runCmd;
/** Action to run, provided through builder method */
private final Action action;
/** ActionParameterHelper instance, configured through builder method */
private final ActionParameterHelper actionParameterHelper;
/** ActionRunnerCommand instance, configured through builder method */
private final ActionRunnerCommand actionRunnerCommand;
private final ActionSourceConfig actionSourceConfig;
private final ActionRuntimeConfig actionRuntimeConfig;

public final CommandLine createCommandLine() {
CommandLine cl = new CommandLine(createCommandSpec());
Expand All @@ -49,35 +42,43 @@ public final CommandLine createCommandLine() {
}

private final CommandSpec createCommandSpec() {
var fullCmdName = String.format("%s %s [run-options]", runCmd, action.getMetadata().getName());
CommandSpec actionCmd = CommandSpec.forAnnotatedObject(actionRunnerCommand)
.name(fullCmdName)
.resourceBundleBaseName("com.fortify.cli.common.i18n.ActionMessages");
var actionParameterHelper = ActionParameterHelper.builder()
.actionSourceConfig(actionSourceConfig)
.actionRuntimeConfig(actionRuntimeConfig)
.build();
CommandSpec actionCmd = CommandSpec.forAnnotatedObject(ActionRunnerCommand.builder()
.action(actionSourceConfig.getAction())
.actionParameterHelper(actionParameterHelper)
.build())
.name(createFullCmdName())
.resourceBundleBaseName("com.fortify.cli.common.i18n.FortifyCLIMessages");
addUsage(actionCmd);
actionParameterHelper.addOptions(actionCmd);
return actionCmd;
}

private String createFullCmdName() {
return String.format("%s %s", actionRuntimeConfig.getActionRunCommand(), actionSourceConfig.asArgsString());
}

private final void addUsage(CommandSpec actionCmd) {
var action = actionSourceConfig.getAction();
actionCmd.usageMessage().header(action.getUsage().getHeader());
actionCmd.usageMessage().description(getUsageDescription());
actionCmd.usageMessage().description(getUsageDescription(action));
}

private final String getUsageDescription() {
return String.format("%s\n\n%s\n\n%s",
private final String getUsageDescription(Action action) {
return String.format("%s\n\n%s",
cleanWhitespace(action.getUsage().getDescription()),
"[run-options] in the synopsis above refers to options documented "+
"on the `"+runCmd+"` command, for example specifying where the "+
"action is loaded from, and signature verification options.",
getMetadataDescription().trim());
getMetadataDescription(action).trim());
}

private Object cleanWhitespace(String s) {
// TODO Improve formatting? Or just have yaml files provide string?
// Remove all newlines except for empty lines, then remove all duplicate spaces.
return s.replaceAll("([^\\n])\\n([^\\n])", "$1 $2").replaceAll("[ ]+", " ").trim();
}

private final String getMetadataDescription() {
private final String getMetadataDescription(Action action) {
var metadata = action.getMetadata();
var signatureDescriptor = metadata.getSignatureDescriptor();
var signatureMetadata = signatureDescriptor==null ? null : signatureDescriptor.getMetadata();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@
* herein. The information contained herein is subject to change
* without notice.
*/
package com.fortify.cli.common.action.helper;
package com.fortify.cli.common.action.runner.n;

import java.util.ArrayList;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
Expand All @@ -22,48 +21,41 @@
import org.slf4j.LoggerFactory;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fortify.cli.common.action.model.Action;
import com.fortify.cli.common.action.model.ActionParameter;
import com.fortify.cli.common.action.runner.ActionRunner.ParameterTypeConverterArgs;
import com.fortify.cli.common.action.runner.ActionSpelFunctions;
import com.fortify.cli.common.json.JsonHelper;
import com.fortify.cli.common.spring.expression.IConfigurableSpelEvaluator;
import com.fortify.cli.common.spring.expression.SpelEvaluator;
import com.fortify.cli.common.spring.expression.SpelHelper;
import com.fortify.cli.common.util.StringUtils;

import lombok.Builder;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Singular;
import picocli.CommandLine.ITypeConverter;
import picocli.CommandLine.Model.ArgGroupSpec;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Model.OptionSpec;

@Builder
public final class ActionParameterHelper {
/** Logger */
private static final Logger LOG = LoggerFactory.getLogger(ActionParameterHelper.class);
/** Static SpEL evaluator configured with {@link ActionSpelFunctions} */
private static final IConfigurableSpelEvaluator spelEvaluator = SpelEvaluator.JSON_GENERIC.copy()
.configure(c->SpelHelper.registerFunctions(c, ActionSpelFunctions.class));

/** Action instance, configured through builder method */
@NonNull private final Action action;
/** Type-based OptionSpec configurers */
@Singular private final Map<String, BiConsumer<OptionSpec.Builder, ActionParameter>> optionSpecTypeConfigurers;
private final ActionSourceConfig actionSourceConfig;
private final ActionRuntimeConfig actionRuntimeConfig;
/** What action to take on unknown parameter types, configured through builder method */
private final OnUnknownParameterType onUnknownParameterType;

public final void addOptions(CommandSpec actionCmd) {
var action = actionSourceConfig.getAction();
var actionArgGroup = ArgGroupSpec.builder()
.exclusive(false)
.heading("Action Options\n")
.multiplicity("1");
for ( var p : action.getParameters() ) {
actionCmd.addOption(createOptionSpec(p));
actionArgGroup.addArg(createOptionSpec(p));
}
actionCmd.addArgGroup(actionArgGroup.build());
}

public final ObjectNode getParameterValues(CommandSpec actionCmd, Function<ActionParameter, ParameterTypeConverterArgs> parameterTypeConverterArgsSupplier) {
var action = actionSourceConfig.getAction();
ObjectNode result = JsonHelper.getObjectMapper().createObjectNode();
for ( var p : action.getParameters() ) {
var name = p.getName();
Expand All @@ -82,7 +74,7 @@ private final OptionSpec createOptionSpec(ActionParameter p) {
var builder = OptionSpec.builder(getOptionNames(p));
configureType(builder, p);
if ( p.getDefaultValue()!=null ) {
builder.defaultValue(spelEvaluator.evaluate(p.getDefaultValue(), null, String.class));
builder.defaultValue(actionRuntimeConfig.createSpelEvaluator().evaluate(p.getDefaultValue(), null, String.class));
}
builder.description(p.getDescription());
builder.required(p.isRequired());
Expand All @@ -91,7 +83,7 @@ private final OptionSpec createOptionSpec(ActionParameter p) {

private void configureType(OptionSpec.Builder builder, ActionParameter p) {
var type = p.getType();
var configurer = optionSpecTypeConfigurers.get(StringUtils.ifBlank(type, "string"));
var configurer = actionRuntimeConfig.getOptionSpecTypeConfigurer(StringUtils.ifBlank(type, "string"));
if ( configurer==null ) {
(onUnknownParameterType==null ? OnUnknownParameterType.ERROR : onUnknownParameterType).configure(builder, p);
} else {
Expand Down Expand Up @@ -137,31 +129,6 @@ private static final String getMessage(ActionParameter p) {
}
}

public static class ActionParameterHelperBuilder {
public ActionParameterHelperBuilder() {
addDefaultOptionSpecTypeConfigurers();
}

private final void addDefaultOptionSpecTypeConfigurers() {
optionSpecTypeConfigurer("string", (b,p)->b.arity("1").type(String.class));
optionSpecTypeConfigurer("boolean", (b,p)->b.arity("0..1").type(Boolean.class).defaultValue("false"));
optionSpecTypeConfigurer("int", (b,p)->b.arity("1").type(Integer.class));
optionSpecTypeConfigurer("long", (b,p)->b.arity("1").type(Long.class));
optionSpecTypeConfigurer("double", (b,p)->b.arity("1").type(Double.class));
optionSpecTypeConfigurer("float", (b,p)->b.arity("1").type(Float.class));
optionSpecTypeConfigurer("array", (b,p)->b.arity("1").type(ArrayNode.class).converters(new ArrayNodeConverter()));
}
}

private static final class ArrayNodeConverter implements ITypeConverter<ArrayNode> {
@Override
public ArrayNode convert(String value) throws Exception {
return StringUtils.isBlank(value)
? JsonHelper.toArrayNode(new String[] {})
: JsonHelper.toArrayNode(value.split(","));
}
}

// TODO What values should we store in this class (like ActionParameter object),
// and what values should be passed on the getValue method?
@RequiredArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@
* herein. The information contained herein is subject to change
* without notice.
*/
package com.fortify.cli.common.action.runner;
package com.fortify.cli.common.action.runner.n;

import java.util.concurrent.Callable;

import com.fortify.cli.common.action.helper.ActionParameterHelper;
import com.fortify.cli.common.action.cli.mixin.ActionValidationMixin;
import com.fortify.cli.common.action.model.Action;
import com.fortify.cli.common.progress.cli.mixin.ProgressWriterFactoryMixin;

import lombok.Builder;
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Spec;

Expand All @@ -30,6 +32,12 @@ public class ActionRunnerCommand implements Callable<Integer> {
/** ActionParameterHelper instance, configured through builder method */
private final ActionParameterHelper actionParameterHelper;
@Spec private CommandSpec spec;
// Not used, but duplicates command-line options from run command in action help/doc
// TODO We should probably have a single mixin that's shared between this and
// AbstractActionRunCommand, but it shouldn't include ActionResolverMixin
// as this is handled separately.
@Mixin private ProgressWriterFactoryMixin progressWriterFactory;
@Mixin private ActionValidationMixin actionValidationMixin;

public Integer call() {
System.out.println(spec);
Expand Down
Loading

0 comments on commit 2d74955

Please sign in to comment.