diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/cli/cmd/AbstractActionAsciidocCommand.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/cli/cmd/AbstractActionAsciidocCommand.java new file mode 100644 index 0000000000..590261be66 --- /dev/null +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/cli/cmd/AbstractActionAsciidocCommand.java @@ -0,0 +1,146 @@ +/******************************************************************************* + * Copyright 2021, 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 java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.fortify.cli.common.action.cli.mixin.ActionSourceResolverMixin; +import com.fortify.cli.common.action.helper.ActionLoaderHelper; +import com.fortify.cli.common.action.helper.ActionLoaderHelper.ActionValidationHandler; +import com.fortify.cli.common.action.model.Action; +import com.fortify.cli.common.action.runner.ActionParameterHelper; +import com.fortify.cli.common.cli.cmd.AbstractRunnableCommand; +import com.fortify.cli.common.cli.mixin.CommonOptionMixins; +import com.fortify.cli.common.cli.util.SimpleOptionsParser.IOptionDescriptor; +import com.fortify.cli.common.util.FcliBuildPropertiesHelper; +import com.fortify.cli.common.util.StringUtils; + +import lombok.SneakyThrows; +import picocli.CommandLine.Mixin; +import picocli.CommandLine.Option; + +public abstract class AbstractActionAsciidocCommand extends AbstractRunnableCommand { + @Mixin private ActionSourceResolverMixin.OptionalOption actionSourceResolver; + @Mixin private CommonOptionMixins.OptionalFile outputFileMixin; + @Option(names= {"--manpage-dir", "-d"}, required = false, descriptionKey="fcli.action.asciidoc.manpage-dir") + private Path manpageDir; + + @Override @SneakyThrows + public final Integer call() { + initMixins(); + var contents = generateHeader(); + contents += ActionLoaderHelper + .streamAsActions(actionSourceResolver.getActionSources(getType()), ActionValidationHandler.IGNORE) + .map(this::generateActionSection) + .collect(Collectors.joining("\n\n")); + contents = addLinks(contents); + var outputFile = outputFileMixin.getFile(); + if ( outputFile==null ) { + System.out.println(contents); + } else { + // TODO Should we require confirmation is file already exists? + Files.writeString(outputFile.toPath(), contents, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING); + } + return 0; + } + + private final String replaceVariables(String s) { + return s.replace("${version}", FcliBuildPropertiesHelper.getFcliBuildInfo().replace(':', ' ')) + .replace("${type}", getType()) + .replace("${typeLower}", getType().toLowerCase()); + } + + private final String generateHeader() { + return replaceVariables(""" + = Fcli ${type} Actions + + This manual page describes built-in fcli ${type} actions that can be run through + the `fcli ${typeLower} action run ` command. + + """); + } + + private final String generateActionSection(Action action) { + // TODO Generate proper options list in synopsis. We should have a re-usable method in + // ActionParameterHelper or other class for generating this, such that we can also + // show synopsis in `fcli * action help` output. + String name = action.getMetadata().getName(); + return replaceVariables(String.format(""" + == %s + + %s + + === Synopsis + + *fcli ${typeLower} action run %s * + + === Description + + %s + + === Options + + %s + + """, name, action.getUsage().getHeader(), name, action.getUsage().getDescription(), generateOptionsSection(action))); + } + + private final String generateOptionsSection(Action action) { + return ActionParameterHelper.getOptionDescriptors(action) + .stream().map(this::generateOptionDescription).collect(Collectors.joining("\n\n")); + } + + private final String generateOptionDescription(IOptionDescriptor descriptor) { + return String.format("%s::\n%s", + descriptor.getOptionNamesAndAliasesString(", "), + StringUtils.indent(descriptor.getDescription(), " ")); + } + + private final String addLinks(String contents) { + if ( manpageDir==null ) { return contents; } + var manPages = listDir(manpageDir).stream().filter(s->s.matches("fcli-[\\w-]+-[\\w-]+-[\\w-]+.adoc")) + .map(s->s.replaceAll("\\.adoc", "")) + .collect(Collectors.toSet()); + for ( var manPage : manPages ) { + var pattern = manPage.replace("-", "[ -]"); + var replacement = String.format("link:manpage/%s.html[$1]", manPage); + contents = contents.replaceAll("(? listDir(Path dir) { + try (Stream stream = Files.list(dir)) { + return stream + .filter(file -> !Files.isDirectory(file)) + .map(Path::getFileName) + .map(Path::toString) + .collect(Collectors.toSet()); + } catch ( IOException e ) { + return new HashSet<>(); + } + } + + protected abstract String getType(); + + +} diff --git a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionLoaderHelper.java b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionLoaderHelper.java index a0de259daf..edb0b1c020 100644 --- a/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionLoaderHelper.java +++ b/fcli-core/fcli-common/src/main/java/com/fortify/cli/common/action/helper/ActionLoaderHelper.java @@ -26,6 +26,7 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Supplier; import java.util.regex.Pattern; import java.util.stream.Stream; @@ -73,19 +74,23 @@ public static final ActionLoadResult load(List sources, String nam return new ActionLoader(sources, actionValidationHandler).load(name); } + public static final Stream streamAsActions(List sources, ActionValidationHandler actionValidationHandler) { + return _stream(sources, actionValidationHandler, ActionLoadResult::getAction, a->a.getMetadata().getName()); + } + public static final Stream streamAsJson(List sources, ActionValidationHandler actionValidationHandler) { - return _streamAsJson(sources, actionValidationHandler); + return _stream(sources, actionValidationHandler, ActionLoadResult::getSummaryObjectNode, o->o.get("name").asText()); } - private static final Stream _streamAsJson(List sources, ActionValidationHandler actionValidationHandler) { - Map result = new HashMap<>(); + private static final Stream _stream(List sources, ActionValidationHandler actionValidationHandler, Function asTypeFunction, Function nameFunction) { + Map result = new HashMap<>(); new ActionLoader(sources, actionValidationHandler) .processActions(loadResult->{ - result.putIfAbsent(loadResult.getMetadata().getName(), loadResult.getSummaryObjectNode()); + result.putIfAbsent(loadResult.getMetadata().getName(), asTypeFunction.apply(loadResult)); return Break.FALSE; }); return result.values().stream() - .sorted((a,b)->a.get("name").asText().compareTo(b.get("name").asText())); + .sorted((a,b)->nameFunction.apply(a).compareTo(nameFunction.apply(b))); } public static final String getSignatureStatusMessage(ActionMetadata metadata, SignatureStatus signatureStatus) { diff --git a/fcli-core/fcli-common/src/main/resources/com/fortify/cli/common/i18n/FortifyCLIMessages.properties b/fcli-core/fcli-common/src/main/resources/com/fortify/cli/common/i18n/FortifyCLIMessages.properties index 6048f2d814..57e07d52f1 100644 --- a/fcli-core/fcli-common/src/main/resources/com/fortify/cli/common/i18n/FortifyCLIMessages.properties +++ b/fcli-core/fcli-common/src/main/resources/com/fortify/cli/common/i18n/FortifyCLIMessages.properties @@ -31,6 +31,9 @@ log-file = File where logging data will be written. Defaults to fcli.log in curr fcli.action.nameOrLocation = The action to load; either simple name or local or remote action \ YAML file location. Note that custom actions are currently considered PREVIEW functionality, \ as explained in the 'fcli ${module} action -h' help output. +fcli.action.asciidoc.manpage-dir = Optional directory to write output. If directory contains fcli \ + manual pages, any (full) fcli commands in the generated documentation will link to the corresponding \ + fcli manual page. fcli.action.run.action-parameter = Action parameter(s); see 'help' command output to \ list supported parameters. @@ -53,8 +56,8 @@ fcli.action.sign.pubout = Public key output file. This option is required when g new key pair (if given private key doesn't exist), and may optionally be used for outputting \ the public key if an already existing private key is being used. fcli.action.resolver.from-zip = Optional local or remote zip-file from which to load the action if \ - the action is specified as a simple name. This option will be ignored if action is specified as \ - local or remote action YAML file location. + the action is specified as a simple name. For commands that take an action as input (like get, help \ + or run), this option will be ignored if action is specified as local or remote action YAML file location. fcli.action.resolver.pubkey = Optional public key to use for verifying action signature. Can \ be specified as one of: \ %n file:%n url:%n string:%n env:\ diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/action/cli/cmd/FoDActionAsciidocCommand.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/action/cli/cmd/FoDActionAsciidocCommand.java new file mode 100644 index 0000000000..889bb94d92 --- /dev/null +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/action/cli/cmd/FoDActionAsciidocCommand.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright 2021, 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.fod.action.cli.cmd; + +import com.fortify.cli.common.action.cli.cmd.AbstractActionAsciidocCommand; + +import picocli.CommandLine.Command; + +@Command(name = "asciidoc", hidden = true) +public class FoDActionAsciidocCommand extends AbstractActionAsciidocCommand { + @Override + protected final String getType() { + return "FoD"; + } +} diff --git a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/action/cli/cmd/FoDActionCommands.java b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/action/cli/cmd/FoDActionCommands.java index f4b9e6cbff..33e2d59a58 100644 --- a/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/action/cli/cmd/FoDActionCommands.java +++ b/fcli-core/fcli-fod/src/main/java/com/fortify/cli/fod/action/cli/cmd/FoDActionCommands.java @@ -19,6 +19,7 @@ @Command( name = "action", subcommands = { + FoDActionAsciidocCommand.class, FoDActionGetCommand.class, FoDActionHelpCommand.class, FoDActionImportCommand.class, diff --git a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/i18n/FoDMessages.properties b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/i18n/FoDMessages.properties index 9f5813d72e..c5a86f16ad 100644 --- a/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/i18n/FoDMessages.properties +++ b/fcli-core/fcli-fod/src/main/resources/com/fortify/cli/fod/i18n/FoDMessages.properties @@ -158,6 +158,8 @@ fcli.fod.rest.lookup.[0] = The type of lookup items to return. Valid value # and descriptions are the same as for other action modules like SSC. When updating here, # the same updates should be made in other modules. fcli.fod.action.usage.header = Manage FoD actions: data export, integration, automation & more. +fcli.fod.action.asciidoc.usage.header = Generate action Asciidoc documentation. +fcli.fod.action.asciidoc.file = Optional file where output will be written. fcli.fod.action.get.usage.header = Get action contents. fcli.fod.action.help.usage.header = Show action usage help. fcli.fod.action.import.usage.header = Import custom actions. diff --git a/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/action/cli/cmd/SSCActionAsciidocCommand.java b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/action/cli/cmd/SSCActionAsciidocCommand.java new file mode 100644 index 0000000000..fd11e5d793 --- /dev/null +++ b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/action/cli/cmd/SSCActionAsciidocCommand.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright 2021, 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.ssc.action.cli.cmd; + +import com.fortify.cli.common.action.cli.cmd.AbstractActionAsciidocCommand; + +import picocli.CommandLine.Command; + +@Command(name = "asciidoc", hidden = true) +public class SSCActionAsciidocCommand extends AbstractActionAsciidocCommand { + @Override + protected final String getType() { + return "SSC"; + } +} diff --git a/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/action/cli/cmd/SSCActionCommands.java b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/action/cli/cmd/SSCActionCommands.java index aacfdb5c95..7021d5fd71 100644 --- a/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/action/cli/cmd/SSCActionCommands.java +++ b/fcli-core/fcli-ssc/src/main/java/com/fortify/cli/ssc/action/cli/cmd/SSCActionCommands.java @@ -19,6 +19,7 @@ @Command( name = "action", subcommands = { + SSCActionAsciidocCommand.class, SSCActionGetCommand.class, SSCActionHelpCommand.class, SSCActionImportCommand.class, diff --git a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/i18n/SSCMessages.properties b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/i18n/SSCMessages.properties index 572bf763f0..79ef6b9c31 100644 --- a/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/i18n/SSCMessages.properties +++ b/fcli-core/fcli-ssc/src/main/resources/com/fortify/cli/ssc/i18n/SSCMessages.properties @@ -141,6 +141,8 @@ fcli.ssc.rest.call.transform = This option allows for performing custom transfor # and descriptions are the same as for other action modules like FoD. When updating here, # the same updates should be made in other modules. fcli.ssc.action.usage.header = Manage SSC actions: data export, integration, automation & more. +fcli.ssc.action.asciidoc.usage.header = Generate action Asciidoc documentation. +fcli.ssc.action.asciidoc.file = Asciidoc output file. fcli.ssc.action.get.usage.header = Get action contents. fcli.ssc.action.help.usage.header = Show action usage help. fcli.ssc.action.import.usage.header = Import custom actions. diff --git a/fcli-other/fcli-doc/build.gradle b/fcli-other/fcli-doc/build.gradle index 41434c1075..dabc6bd81c 100644 --- a/fcli-other/fcli-doc/build.gradle +++ b/fcli-other/fcli-doc/build.gradle @@ -66,9 +66,27 @@ task prepareAsciiDocForVersionedHtml(type: Copy) { } } +task generateSSCActionAsciiDoc(type: JavaExec) { + dependsOn(generateManpageAsciiDoc, prepareAsciiDocForVersionedHtml) + group = "Documentation" + description = "Generate SSC Action AsciiDoc" + classpath(configurations.runtimeClasspath, configurations.annotationProcessor) + main 'com.fortify.cli.app.FortifyCLI' + args "ssc", "action", "asciidoc", "-d=${manpageAsciiDocDir}", "-f=${htmlAsciiDocDir}/ssc-actions.adoc" +} + +task generateFoDActionAsciiDoc(type: JavaExec) { + dependsOn(generateManpageAsciiDoc, prepareAsciiDocForVersionedHtml) + group = "Documentation" + description = "Generate FoD Action AsciiDoc" + classpath(configurations.runtimeClasspath, configurations.annotationProcessor) + main 'com.fortify.cli.app.FortifyCLI' + args "fod", "action", "asciidoc", "-d=${manpageAsciiDocDir}", "-f=${htmlAsciiDocDir}/fod-actions.adoc" +} + // Generate HTML documentation from AsciiDoc prepared by prepareAsciiDocForVersionedHtml task asciiDoctorVersionedHtml(type: org.asciidoctor.gradle.jvm.AsciidoctorTask) { - dependsOn(prepareAsciiDocForVersionedHtml) + dependsOn(prepareAsciiDocForVersionedHtml, generateSSCActionAsciiDoc, generateFoDActionAsciiDoc) forkOptions { jvmArgs("--add-opens","java.base/sun.nio.ch=ALL-UNNAMED","--add-opens","java.base/java.io=ALL-UNNAMED") }