Skip to content

Commit

Permalink
docs: Add usage help for built-in actions to HTML documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
rsenden committed Sep 16, 2024
1 parent ae795c5 commit b7a1705
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -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 <action-name>` 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 <options>*
=== 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("(?<!`)("+pattern+")", replacement);
contents = contents.replaceAll("(`"+pattern+".*`)", replacement);
}
return contents;
}

private Set<String> listDir(Path dir) {
try (Stream<Path> 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();


}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -73,19 +74,23 @@ public static final ActionLoadResult load(List<ActionSource> sources, String nam
return new ActionLoader(sources, actionValidationHandler).load(name);
}

public static final Stream<Action> streamAsActions(List<ActionSource> sources, ActionValidationHandler actionValidationHandler) {
return _stream(sources, actionValidationHandler, ActionLoadResult::getAction, a->a.getMetadata().getName());
}

public static final Stream<ObjectNode> streamAsJson(List<ActionSource> sources, ActionValidationHandler actionValidationHandler) {
return _streamAsJson(sources, actionValidationHandler);
return _stream(sources, actionValidationHandler, ActionLoadResult::getSummaryObjectNode, o->o.get("name").asText());
}

private static final Stream<ObjectNode> _streamAsJson(List<ActionSource> sources, ActionValidationHandler actionValidationHandler) {
Map<String, ObjectNode> result = new HashMap<>();
private static final <T> Stream<T> _stream(List<ActionSource> sources, ActionValidationHandler actionValidationHandler, Function<ActionLoadResult, T> asTypeFunction, Function<T, String> nameFunction) {
Map<String, T> 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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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:<local file>%n url:<url>%n string:<string value>%n env:<env-var name>\
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
@Command(
name = "action",
subcommands = {
FoDActionAsciidocCommand.class,
FoDActionGetCommand.class,
FoDActionHelpCommand.class,
FoDActionImportCommand.class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ fcli.fod.rest.lookup.<type>[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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
@Command(
name = "action",
subcommands = {
SSCActionAsciidocCommand.class,
SSCActionGetCommand.class,
SSCActionHelpCommand.class,
SSCActionImportCommand.class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
20 changes: 19 additions & 1 deletion fcli-other/fcli-doc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down

0 comments on commit b7a1705

Please sign in to comment.