This is an automated email from the ASF dual-hosted git repository.

ddekany pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/freemarker-docgen.git


The following commit(s) were added to refs/heads/master by this push:
     new b21e4dd  - Feature to execute a Java main method and insert its output 
into the documentation now works - Improved castSetting API
b21e4dd is described below

commit b21e4dde5e3c7b0652f06700b6b9b12f891bd9a7
Author: ddekany <[email protected]>
AuthorDate: Mon Feb 8 08:59:48 2021 +0100

    - Feature to execute a Java main method and insert its output into the 
documentation now works
    - Improved castSetting API
---
 .../org/freemarker/docgen/core/DefaultValue.java   |  34 +++
 .../PrintTextWithDocgenSubstitutionsDirective.java | 234 +++++++++++++++++----
 .../org/freemarker/docgen/core/SettingUtils.java   |  14 +-
 .../java/org/freemarker/docgen/core/Transform.java | 125 ++++++++---
 .../freemarker/docgen/core/SettingUtilsTest.java   |   4 +-
 5 files changed, 329 insertions(+), 82 deletions(-)

diff --git 
a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/DefaultValue.java
 
b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/DefaultValue.java
new file mode 100644
index 0000000..9026834
--- /dev/null
+++ 
b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/DefaultValue.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.freemarker.docgen.core;
+
+final class DefaultValue<T> {
+    private final T value;
+
+    static final DefaultValue NULL = new DefaultValue(null);
+
+    public DefaultValue(T value) {
+        this.value = value;
+    }
+
+    public T get() {
+        return value;
+    }
+}
diff --git 
a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/PrintTextWithDocgenSubstitutionsDirective.java
 
b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/PrintTextWithDocgenSubstitutionsDirective.java
index 55adcbf..624f66c 100644
--- 
a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/PrintTextWithDocgenSubstitutionsDirective.java
+++ 
b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/PrintTextWithDocgenSubstitutionsDirective.java
@@ -19,14 +19,23 @@
 
 package org.freemarker.docgen.core;
 
+import static 
org.freemarker.docgen.core.PrintTextWithDocgenSubstitutionsDirective.InsertDirectiveType.*;
+
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.io.Writer;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 import java.nio.charset.UnsupportedCharsetException;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -35,11 +44,15 @@ import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.regex.PatternSyntaxException;
+import java.util.stream.Collectors;
 
 import org.apache.commons.io.FilenameUtils;
 import org.apache.commons.io.IOUtils;
+import org.apache.commons.io.output.WriterOutputStream;
 import org.apache.commons.text.StringEscapeUtils;
 
+import com.google.common.collect.ImmutableList;
+
 import freemarker.core.Environment;
 import freemarker.core.HTMLOutputFormat;
 import freemarker.core.NonStringException;
@@ -63,8 +76,17 @@ public class PrintTextWithDocgenSubstitutionsDirective 
implements TemplateDirect
     private static final String DOCGEN_TAG_START = "[docgen";
     private static final String DOCGEN_TAG_END = "]";
     private static final String DOCGEN_END_TAG_START = "[/docgen";
-    private static final String INSERT_FILE = "insertFile";
-    private static final String INSERT_OUTPUT = "insertOutput";
+
+    enum InsertDirectiveType {
+        INSERT_FILE("insertFile"),
+        INSERT_OUTPUT("insertOutput");
+
+        private final String directiveName;
+
+        InsertDirectiveType(String directiveName) {
+            this.directiveName = directiveName;
+        }
+    }
 
     private final Transform transform;
 
@@ -105,6 +127,9 @@ public class PrintTextWithDocgenSubstitutionsDirective 
implements TemplateDirect
         new DocgenSubstitutionInterpreter(text, env).execute();
     }
 
+    private static final String DOCGEN_WD_TAG = "[docgen:wd]";
+    private static final Pattern DOCGEN_WD_TAG_AND_SLASH_PATTERN = 
Pattern.compile(Pattern.quote(DOCGEN_WD_TAG) +  "/?");
+
     private class DocgenSubstitutionInterpreter {
         private final String text;
         private final Environment env;
@@ -142,12 +167,12 @@ public class PrintTextWithDocgenSubstitutionsDirective 
implements TemplateDirect
                     lastUnprintedIdx = cursor;
 
                     insertCustomVariable(customVarName);
-                } else if (INSERT_FILE.equals(subvarName)) {
-                    InsertDirectiveArgs args = 
fetchInsertDirectiveArgs(subvarName, true, true, false);
+                } else if (INSERT_FILE.directiveName.equals(subvarName)) {
+                    InsertDirectiveArgs args = 
fetchInsertDirectiveArgs(subvarName, INSERT_FILE);
                     lastUnprintedIdx = cursor;
                     insertFile(args);
-                } else if (INSERT_OUTPUT.equals(subvarName)) {
-                    InsertDirectiveArgs args = 
fetchInsertDirectiveArgs(subvarName, false, false, true);
+                } else if (INSERT_OUTPUT.directiveName.equals(subvarName)) {
+                    InsertDirectiveArgs args = 
fetchInsertDirectiveArgs(subvarName, INSERT_OUTPUT);
                     lastUnprintedIdx = cursor;
                     insertOutput(args);
                 } else {
@@ -257,43 +282,22 @@ public class PrintTextWithDocgenSubstitutionsDirective 
implements TemplateDirect
                     fileContent = removeFTLCopyrightComment(fileContent);
                 }
 
-                if (args.from != null) {
-                    Matcher matcher = args.from.matcher(fileContent);
-                    if (matcher.find()) {
-                        String remaining = 
fileContent.substring(matcher.start());
-                        fileContent = "[\u2026]"
-                                + (remaining.startsWith("\n") || 
remaining.startsWith("\r") ? "" : "\n")
-                                + remaining;
-                    } else if (!args.fromOptional) {
-                        throw newErrorInDocgenTag(
-                                "\"from\" regular expression has no match in 
the file content: " + args.from);
-                    }
-                }
-
-                if (args.to != null) {
-                    Matcher matcher = args.to.matcher(fileContent);
-                    if (matcher.find()) {
-                        String remaining = fileContent.substring(0, 
matcher.start());
-                        fileContent = remaining
-                                + (remaining.endsWith("\n") || 
remaining.endsWith("\r") ? "" : "\n")
-                                + "[\u2026]";
-                    } else if (!args.toOptional) {
-                        throw newErrorInDocgenTag(
-                                "\"to\" regular expression has no match in the 
file content: " + args.to);
-                    }
-                }
-
-                HTMLOutputFormat.INSTANCE.output(fileContent, out);
+                cutAndInsertContent(args, fileContent);
             }
         }
 
         private void insertOutput(InsertDirectiveArgs args) throws 
TemplateException, IOException {
+            if (args.printCommand) {
+                out.write("> ");
+                
out.write(DOCGEN_WD_TAG_AND_SLASH_PATTERN.matcher(StringUtil.chomp(args.body)).replaceAll(""));
+                out.write("\n");
+            }
+
             List<String> splitCmdLine = 
BashCommandLineArgsParser.parse(args.body);
             if (splitCmdLine.isEmpty()) {
                 throw newErrorInDocgenTag("Command to execute was empty");
             }
             String cmdKey = splitCmdLine.get(0);
-            List<String> cmdArgs = splitCmdLine.subList(1, 
splitCmdLine.size());
             Map<String, Transform.InsertableOutputCommandProperties> 
cmdPropsMap =
                     transform.getInsertableOutputCommands();
             Transform.InsertableOutputCommandProperties cmdProps = 
cmdPropsMap.get(cmdKey);
@@ -306,14 +310,134 @@ public class PrintTextWithDocgenSubstitutionsDirective 
implements TemplateDirect
                                         ? "That setting is empty."
                                         : "It has these keys: " + 
String.join(", ", cmdPropsMap.keySet())));
             }
-            HTMLOutputFormat.INSTANCE.output("!!T\n" + cmdProps + "\n" + 
cmdArgs, out);
+
+            Method mainMethod = getMainMethod(cmdKey, cmdProps);
+
+            StringWriter stdOutCapturer;
+            PrintStream prevOut = System.out;
+            Map<String, String> prevSystemProperties = new HashMap<>();
+            try {
+                stdOutCapturer = new StringWriter();
+                PrintStream stdOutCapturerPrintStream = new PrintStream(
+                        new WriterOutputStream(stdOutCapturer, 
Charset.defaultCharset()));
+                System.setOut(stdOutCapturerPrintStream);
+
+                cmdProps.getSystemProperties().forEach((k, v) -> {
+                    String prevValue = setOrClearSystemProperty(k, v);
+                    prevSystemProperties.put(k, prevValue);
+                });
+
+                List<String> rawCmdArgs = splitCmdLine.subList(1, 
splitCmdLine.size());
+                List<String> cmdArgs = ImmutableList.<String>builder()
+                        .addAll(cmdProps.getPrependedArguments())
+                        .addAll(rawCmdArgs)
+                        .addAll(cmdProps.getAppendedArguments())
+                        .build().stream()
+                        .map(cmdArg -> {
+                            Path wdSubst = cmdProps.getWdSubstitution();
+                            if (wdSubst == null) {
+                                return cmdArg;
+                            }
+                            return cmdArg.replace(DOCGEN_WD_TAG, 
wdSubst.toAbsolutePath().toString());
+                        })
+                        .collect(Collectors.toList());
+
+                Object returnValue;
+                try {
+                    returnValue = mainMethod.invoke(null, (Object) 
cmdArgs.toArray(new String[0]));
+                } catch (Exception e) {
+                    throw newErrorInDocgenTag("Error when executing command 
with "
+                                    + cmdProps.getMainClassName() + "." + 
cmdProps.getMainMethodName()
+                                    + ", and arguments " + cmdArgs + ".",
+                            e);
+                }
+                if (returnValue instanceof Integer && ((Integer) returnValue) 
!= 0) {
+                    throw newErrorInDocgenTag(
+                            "Command execution has returned with non-0 exit 
code " + returnValue
+                                    + ", from " + cmdProps.getMainClassName() 
+ "." + cmdProps.getMainMethodName()
+                                    + ", called with arguments " + cmdArgs + 
".");
+                }
+
+                stdOutCapturerPrintStream.flush();
+            } finally {
+                
prevSystemProperties.forEach(PrintTextWithDocgenSubstitutionsDirective::setOrClearSystemProperty);
+                System.setOut(prevOut);
+            }
+            cutAndInsertContent(args, stdOutCapturer.toString());
+        }
+
+        private void cutAndInsertContent(InsertDirectiveArgs args, String 
content)
+                throws TemplateException, IOException {
+            if (args.from != null) {
+                Matcher matcher = args.from.matcher(content);
+                if (matcher.find()) {
+                    String remaining = content.substring(matcher.start());
+                    content = "[\u2026]"
+                            + (remaining.startsWith("\n") || 
remaining.startsWith("\r") ? "" : "\n")
+                            + remaining;
+                } else if (!args.fromOptional) {
+                    throw newErrorInDocgenTag(
+                            "\"from\" regular expression has no match in the 
file content: " + args.from);
+                }
+            }
+
+            if (args.to != null) {
+                Matcher matcher = args.to.matcher(content);
+                if (matcher.find()) {
+                    String remaining = content.substring(0, matcher.start());
+                    content = remaining
+                            + (remaining.endsWith("\n") || 
remaining.endsWith("\r") ? "" : "\n")
+                            + "[\u2026]";
+                } else if (!args.toOptional) {
+                    throw newErrorInDocgenTag(
+                            "\"to\" regular expression has no match in the 
file content: " + args.to);
+                }
+            }
+
+            HTMLOutputFormat.INSTANCE.output(content, out);
         }
 
-        private TemplateException newFormattingFailedException(String 
customVarName, TemplateValueFormatException e) {
-            return new TemplateException(
-                    "Formatting failed for Docgen custom variable "
-                            + StringUtil.jQuote(customVarName),
-                    e, env);
+        private Method getMainMethod(String cmdKey, 
Transform.InsertableOutputCommandProperties cmdProps) throws
+                TemplateException {
+            String mainClassName = cmdProps.getMainClassName();
+            Class<?> mainClass;
+            try {
+                mainClass = 
Transform.class.getClassLoader().loadClass(mainClassName);
+            } catch (Exception e) {
+                throw newErrorInDocgenTag(
+                        "The main class referred by "
+                                + Transform.SETTING_INSERTABLE_OUTPUT_COMMANDS 
+ "[" + StringUtil.jQuote(cmdKey) + "], "
+                                + StringUtil.jQuote(mainClassName) + ", 
couldn't be loaded",
+                        e);
+            }
+
+            String mainMethodName = cmdProps.getMainMethodName();
+            Method mainMethod;
+            try {
+                mainMethod = mainClass.getMethod(mainMethodName, 
String[].class);
+            } catch (Exception e) {
+                throw newErrorInDocgenTag(
+                        "Couldn't get " + mainMethodName + "(String[]) method 
from class "
+                                + mainClassName + ".",
+                        e);
+            }
+            if ((mainMethod.getModifiers() & Modifier.STATIC) == 0) {
+                throw newErrorInDocgenTag(
+                        mainMethodName + "(String[]) method from class "
+                                + mainClassName + " must be static.");
+            }
+            if ((mainMethod.getModifiers() & Modifier.PUBLIC) == 0) {
+                throw newErrorInDocgenTag(
+                        mainMethodName + "(String[]) method from class "
+                                + mainClassName + " must be public.");
+            }
+            Class<?> returnType = mainMethod.getReturnType();
+            if (returnType != void.class && returnType != int.class) {
+                throw newErrorInDocgenTag(
+                        mainMethodName + "(String[]) method from class "
+                                + mainClassName + " must return void or int, 
but return type was " + returnType);
+            }
+            return mainMethod;
         }
 
         private int findNextDocgenTagStart(int fromIndex) {
@@ -460,7 +584,6 @@ public class PrintTextWithDocgenSubstitutionsDirective 
implements TemplateDirect
             }
         }
 
-
         private char charAt(int index) {
             return index < text.length() ? text.charAt(index) : 0;
         }
@@ -472,19 +595,30 @@ public class PrintTextWithDocgenSubstitutionsDirective 
implements TemplateDirect
         }
 
         private TemplateException newErrorInDocgenTag(String errorDetail) {
+            return newErrorInDocgenTag(errorDetail, null);
+        }
+
+        private TemplateException newErrorInDocgenTag(String errorDetail, 
Throwable cause) {
             return new DocgenTagException(
-                    "\nError in docgen tag: " + 
text.substring(lastDocgenTagStart, cursor) + "\n" + errorDetail,
+                    "\nError in docgen tag: " + 
text.substring(lastDocgenTagStart, cursor) + "\n" + errorDetail
+                            + (cause != null ? "\nSee cause exception for 
more!" : ""),
+                    cause,
                     env);
+        }
 
+        private TemplateException newFormattingFailedException(String 
customVarName, TemplateValueFormatException e) {
+            return new TemplateException(
+                    "Formatting failed for Docgen custom variable " + 
StringUtil.jQuote(customVarName),
+                    e, env);
         }
 
         private InsertDirectiveArgs fetchInsertDirectiveArgs(
-                String subvarName, boolean hasPath, boolean allowCharsetArg, 
boolean hasBodyArg) throws
+                String subvarName, InsertDirectiveType insertDirectiveType) 
throws
                 TemplateException {
             InsertDirectiveArgs args = new InsertDirectiveArgs();
             args.toOptional = true;
 
-            if (hasPath) {
+            if (insertDirectiveType == INSERT_FILE) {
                 skipWS();
                 args.path = fetchRequiredString();
             }
@@ -498,7 +632,7 @@ public class PrintTextWithDocgenSubstitutionsDirective 
implements TemplateDirect
                             "Duplicate docgen." + subvarName +  " parameter " 
+ StringUtil.jQuote(paramName) + ".",
                             env);
                 }
-                if (allowCharsetArg && paramName.equals("charset")) {
+                if (insertDirectiveType == INSERT_FILE && 
paramName.equals("charset")) {
                     args.charset = 
StringEscapeUtils.unescapeXml(fetchRequiredString());
                 } else if (paramName.equals("from")) {
                     args.from = parseRegularExpressionParam(paramName, 
StringEscapeUtils.unescapeXml(fetchRequiredString()));
@@ -508,6 +642,9 @@ public class PrintTextWithDocgenSubstitutionsDirective 
implements TemplateDirect
                     args.fromOptional = fetchRequiredBoolean();
                 } else if (paramName.equals("toOptional")) {
                     args.toOptional = fetchRequiredBoolean();
+                } else if (insertDirectiveType == INSERT_OUTPUT
+                        && paramName.equals("printCommand")) {
+                    args.printCommand = fetchRequiredBoolean();
                 } else {
                     throw new DocgenTagException(
                             "Unsupported docgen." + subvarName +  " parameter 
" + StringUtil.jQuote(paramName) + ".",
@@ -518,7 +655,7 @@ public class PrintTextWithDocgenSubstitutionsDirective 
implements TemplateDirect
             skipRequiredToken(DOCGEN_TAG_END);
             int indexAfterStartTag = cursor;
 
-            if (hasBodyArg) {
+            if (insertDirectiveType == INSERT_OUTPUT) {
                 int endTagIndex = findNextDocgenEndTag(cursor);
                 if (endTagIndex == -1) {
                     throw new DocgenTagException(
@@ -559,6 +696,10 @@ public class PrintTextWithDocgenSubstitutionsDirective 
implements TemplateDirect
 
     }
 
+    private static String setOrClearSystemProperty(String k, String v) {
+        return v != null ? System.setProperty(k, v) : System.clearProperty(k);
+    }
+
     public static String removeFTLCopyrightComment(String ftl) {
         int copyrightPartIdx = ftl.indexOf("Licensed to the Apache Software 
Foundation");
         if (copyrightPartIdx == -1) {
@@ -616,6 +757,7 @@ public class PrintTextWithDocgenSubstitutionsDirective 
implements TemplateDirect
         private boolean toOptional;
         private String body;
         private int indexAfterDirective;
+        private boolean printCommand;
     }
 
 }
diff --git 
a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/SettingUtils.java
 
b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/SettingUtils.java
index c53d905..39c1f59 100644
--- 
a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/SettingUtils.java
+++ 
b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/SettingUtils.java
@@ -76,13 +76,13 @@ final class SettingUtils {
             Object settingValue, Class<T> elementClass) {
         return castSetting(
                 settingName, settingValue,
-                false,
+                null,
                 List.class, new ListItemType(elementClass)
         );
     }
 
     static <T> T castSetting(SettingName settingName, Object settingValue, 
Class<T> valueType) {
-        return castSetting(settingName, settingValue, false, valueType);
+        return castSetting(settingName, settingValue, null, valueType);
     }
 
     /**
@@ -91,23 +91,25 @@ final class SettingUtils {
     static <T> T castSetting(
             SettingName settingName, Object settingValue, Class<T> valueType,
             ContainedValueType... containedValueTypes) {
-        return castSetting(settingName, settingValue, false, valueType, 
containedValueTypes);
+        return castSetting(settingName, settingValue, null, valueType, 
containedValueTypes);
     }
 
     /**
      * @param valueType
      *      The expected type of the value (on the top-level, if it's a 
container)
+     * @param defaultValue
+     *      {@code null} if the setting is required or can't have {@code null} 
value. Non-null otherwise.
      * @param containedValueTypes
      *      The expected type of the contained values, and of the values 
contained inside them, and so on. (This is
      *      separate from {@code valueType} because Java can't match s generic 
return type with the type of the first
      */
     static <T> T castSetting(
             SettingName settingName, Object settingValue,
-            boolean optional,
+            DefaultValue<T> defaultValue,
             Class<T> valueType, ContainedValueType... containedValueTypes) {
         if (settingValue == null) {
-            if (optional) {
-                return null;
+            if (defaultValue != null) {
+                return defaultValue.get();
             }
             throw newNullSettingValueException(settingName);
         }
diff --git 
a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/Transform.java
 
b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/Transform.java
index 5808fac..3d46668 100644
--- 
a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/Transform.java
+++ 
b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/Transform.java
@@ -45,6 +45,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.Optional;
 import java.util.Set;
 import java.util.SortedMap;
 import java.util.TimeZone;
@@ -155,17 +156,24 @@ public final class Transform {
     static final String SETTING_CUSTOM_VARIABLES = "customVariables";
     static final String SETTING_INSERTABLE_FILES = "insertableFiles";
     static final String SETTING_INSERTABLE_OUTPUT_COMMANDS = 
"insertableOutputCommands";
-    static final String SETTING_INSERTABLE_OUTPUT_COMMADS_CLASS_KEY = "class";
-    static final String 
SETTING_INSERTABLE_OUTPUT_COMMADS_PREPENDED_ARGUMENTS_KEY = 
"prependedArguments";
-    static final String SETTING_INSERTABLE_OUTPUT_COMMADS_WORK_DIRECTORY_KEY = 
"workDirectory";
-    static final Set<String> SETTING_INSERTABLE_OUTPUT_COMMADS_OPTIONAL_KEYS;
-    static final Set<String> SETTING_INSERTABLE_OUTPUT_COMMADS_REQUIRED_KEYS;
+    static final String SETTING_INSERTABLE_OUTPUT_COMMANDS_MAIN_CLASS_KEY = 
"mainClass";
+    static final String SETTING_INSERTABLE_OUTPUT_COMMANDS_MAIN_METHOD_KEY = 
"mainMethod";
+    static final String 
SETTING_INSERTABLE_OUTPUT_COMMANDS_SYSTEM_PROPERTIES_KEY = "systemProperties";
+    static final String 
SETTING_INSERTABLE_OUTPUT_COMMANDS_PREPENDED_ARGUMENTS_KEY = 
"prependedArguments";
+    static final String 
SETTING_INSERTABLE_OUTPUT_COMMANDS_APPENDED_ARGUMENTS_KEY = "appendedArguments";
+    static final String 
SETTING_INSERTABLE_OUTPUT_COMMANDS_DOCGEN_WD_REPLACED_WITH_KEY = 
"docgenWdReplacedWith";
+    static final Set<String> SETTING_INSERTABLE_OUTPUT_COMMANDS_OPTIONAL_KEYS;
+    static final Set<String> SETTING_INSERTABLE_OUTPUT_COMMANDS_REQUIRED_KEYS;
     static {
-        SETTING_INSERTABLE_OUTPUT_COMMADS_REQUIRED_KEYS = new 
LinkedHashSet<>();
-        
SETTING_INSERTABLE_OUTPUT_COMMADS_REQUIRED_KEYS.add(SETTING_INSERTABLE_OUTPUT_COMMADS_CLASS_KEY);
-        SETTING_INSERTABLE_OUTPUT_COMMADS_OPTIONAL_KEYS = new 
LinkedHashSet<>();
-        
SETTING_INSERTABLE_OUTPUT_COMMADS_OPTIONAL_KEYS.add(SETTING_INSERTABLE_OUTPUT_COMMADS_PREPENDED_ARGUMENTS_KEY);
-        
SETTING_INSERTABLE_OUTPUT_COMMADS_OPTIONAL_KEYS.add(SETTING_INSERTABLE_OUTPUT_COMMADS_WORK_DIRECTORY_KEY);
+        SETTING_INSERTABLE_OUTPUT_COMMANDS_REQUIRED_KEYS = new 
LinkedHashSet<>();
+        
SETTING_INSERTABLE_OUTPUT_COMMANDS_REQUIRED_KEYS.add(SETTING_INSERTABLE_OUTPUT_COMMANDS_MAIN_CLASS_KEY);
+        
SETTING_INSERTABLE_OUTPUT_COMMANDS_REQUIRED_KEYS.add(SETTING_INSERTABLE_OUTPUT_COMMANDS_MAIN_METHOD_KEY);
+        SETTING_INSERTABLE_OUTPUT_COMMANDS_OPTIONAL_KEYS = new 
LinkedHashSet<>();
+        
SETTING_INSERTABLE_OUTPUT_COMMANDS_OPTIONAL_KEYS.add(SETTING_INSERTABLE_OUTPUT_COMMANDS_SYSTEM_PROPERTIES_KEY);
+        
SETTING_INSERTABLE_OUTPUT_COMMANDS_OPTIONAL_KEYS.add(SETTING_INSERTABLE_OUTPUT_COMMANDS_PREPENDED_ARGUMENTS_KEY);
+        
SETTING_INSERTABLE_OUTPUT_COMMANDS_OPTIONAL_KEYS.add(SETTING_INSERTABLE_OUTPUT_COMMANDS_APPENDED_ARGUMENTS_KEY);
+        SETTING_INSERTABLE_OUTPUT_COMMANDS_OPTIONAL_KEYS.add(
+                
SETTING_INSERTABLE_OUTPUT_COMMANDS_DOCGEN_WD_REPLACED_WITH_KEY);
     }
 
     static final String SETTING_VALIDATION_PROGRAMLISTINGS_REQ_ROLE
@@ -623,27 +631,51 @@ public final class Transform {
                             Map.class,
                             new MapEntryType(String.class, Map.class),
                             new MapEntryType(
-                                    String.class, 
SETTING_INSERTABLE_OUTPUT_COMMADS_REQUIRED_KEYS, 
SETTING_INSERTABLE_OUTPUT_COMMADS_OPTIONAL_KEYS,
+                                    String.class, 
SETTING_INSERTABLE_OUTPUT_COMMANDS_REQUIRED_KEYS, 
SETTING_INSERTABLE_OUTPUT_COMMANDS_OPTIONAL_KEYS,
                                     Object.class, false));
                     for (Entry<String, Map<String, Object>> ent : 
m.entrySet()) {
                         String commandKey = ent.getKey();
                         Map<String, Object> outputCmdProps = ent.getValue();
                         InsertableOutputCommandProperties commandProps = new 
InsertableOutputCommandProperties(
                                 castSetting(
-                                        settingName.subKey(commandKey, 
SETTING_INSERTABLE_OUTPUT_COMMADS_CLASS_KEY),
-                                        
outputCmdProps.get(SETTING_INSERTABLE_OUTPUT_COMMADS_CLASS_KEY),
+                                        settingName.subKey(commandKey,
+                                                
SETTING_INSERTABLE_OUTPUT_COMMANDS_MAIN_CLASS_KEY),
+                                        
outputCmdProps.get(SETTING_INSERTABLE_OUTPUT_COMMANDS_MAIN_CLASS_KEY),
                                         String.class
                                 ),
                                 castSetting(
-                                        settingName.subKey(commandKey, 
SETTING_INSERTABLE_OUTPUT_COMMADS_PREPENDED_ARGUMENTS_KEY),
-                                        
outputCmdProps.get(SETTING_INSERTABLE_OUTPUT_COMMADS_PREPENDED_ARGUMENTS_KEY),
+                                        settingName.subKey(commandKey,
+                                                
SETTING_INSERTABLE_OUTPUT_COMMANDS_MAIN_METHOD_KEY),
+                                        
outputCmdProps.get(SETTING_INSERTABLE_OUTPUT_COMMANDS_MAIN_METHOD_KEY),
+                                        String.class
+                                ),
+                                castSetting(
+                                        settingName.subKey(commandKey, 
SETTING_INSERTABLE_OUTPUT_COMMANDS_SYSTEM_PROPERTIES_KEY),
+                                        
outputCmdProps.get(SETTING_INSERTABLE_OUTPUT_COMMANDS_SYSTEM_PROPERTIES_KEY),
+                                        new 
DefaultValue<>(Collections.emptyMap()),
+                                        Map.class, new 
MapEntryType(String.class, String.class)
+                                ),
+                                castSetting(
+                                        settingName.subKey(commandKey, 
SETTING_INSERTABLE_OUTPUT_COMMANDS_PREPENDED_ARGUMENTS_KEY),
+                                        
outputCmdProps.get(SETTING_INSERTABLE_OUTPUT_COMMANDS_PREPENDED_ARGUMENTS_KEY),
+                                        new 
DefaultValue<>(Collections.emptyList()),
                                         List.class
                                 ),
-                                Paths.get(castSetting(
-                                        settingName.subKey(commandKey, 
SETTING_INSERTABLE_OUTPUT_COMMADS_WORK_DIRECTORY_KEY),
-                                        
outputCmdProps.get(SETTING_INSERTABLE_OUTPUT_COMMADS_WORK_DIRECTORY_KEY),
-                                        String.class
-                                ))
+                                castSetting(
+                                        settingName.subKey(commandKey, 
SETTING_INSERTABLE_OUTPUT_COMMANDS_APPENDED_ARGUMENTS_KEY),
+                                        
outputCmdProps.get(SETTING_INSERTABLE_OUTPUT_COMMANDS_APPENDED_ARGUMENTS_KEY),
+                                        new 
DefaultValue<>(Collections.emptyList()),
+                                        List.class
+                                ),
+                                Optional.ofNullable(
+                                        castSetting(
+                                                settingName.subKey(commandKey,
+                                                        
SETTING_INSERTABLE_OUTPUT_COMMANDS_DOCGEN_WD_REPLACED_WITH_KEY),
+                                                outputCmdProps.get(
+                                                        
SETTING_INSERTABLE_OUTPUT_COMMANDS_DOCGEN_WD_REPLACED_WITH_KEY), 
DefaultValue.NULL,
+                                                String.class
+                                        )
+                                ).map(Paths::get).orElse(null)
                         );
                         insertableOutputCommands.put(commandKey, commandProps);
                     }
@@ -1304,7 +1336,7 @@ public final class Transform {
     private static Logo castMapToLogo(SettingName settingName, Object 
settingValue) {
         Map<String, String> logoMap = castSetting(
                 settingName,
-                settingValue, false,
+                settingValue, null,
                 Map.class,
                 new MapEntryType(String.class, SETTING_LOGO_MAP_KEYS, 
String.class));
         return new Logo(
@@ -2780,21 +2812,58 @@ public final class Transform {
 
     static class InsertableOutputCommandProperties {
         private final String mainClassName;
+        private final String mainMethodName;
+        private final Map<String, String> systemProperties;
         private final List<String> prependedArguments;
-        private final Path workDirectory;
+        private final List<String> appendedArguments;
+        private final Path wdSubstitution;
 
-        public InsertableOutputCommandProperties(String mainClassName, 
List<String> prependedArguments, Path workDirectory) {
+        public InsertableOutputCommandProperties(
+                String mainClassName, String mainMethodName,
+                Map<String, String> systemProperties,
+                List<String> prependedArguments, List<String> 
appendedArguments, Path wdSubstitution) {
             this.mainClassName = mainClassName;
+            this.mainMethodName = mainMethodName;
+            this.systemProperties = systemProperties;
             this.prependedArguments = prependedArguments;
-            this.workDirectory = workDirectory;
+            this.appendedArguments = appendedArguments;
+            this.wdSubstitution = wdSubstitution;
+        }
+
+        public String getMainClassName() {
+            return mainClassName;
+        }
+
+        public String getMainMethodName() {
+            return mainMethodName;
+        }
+
+        public Map<String, String> getSystemProperties() {
+            return systemProperties;
+        }
+
+        public List<String> getPrependedArguments() {
+            return prependedArguments;
+        }
+
+        public List<String> getAppendedArguments() {
+            return appendedArguments;
+        }
+
+        public Path getWdSubstitution() {
+            return wdSubstitution;
         }
 
         @Override
         public String toString() {
-            return "InsertableOutputCommandProperties{"
-                    + "mainClassName='" + mainClassName + '\''
-                    + ", prependedArguments=" + prependedArguments
-                    + ", workDirectory=" + workDirectory + '}';
+            return "InsertableOutputCommandProperties{" +
+                    "mainClassName='" + mainClassName + '\'' +
+                    ", mainMethodName='" + mainMethodName + '\'' +
+                    ", systemProperties=" + systemProperties +
+                    ", prependedArguments=" + prependedArguments +
+                    ", appendedArguments=" + appendedArguments +
+                    ", wdSubstitution=" + wdSubstitution +
+                    '}';
         }
     }
 
diff --git 
a/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/SettingUtilsTest.java
 
b/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/SettingUtilsTest.java
index b615f3b..ae1b86c 100644
--- 
a/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/SettingUtilsTest.java
+++ 
b/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/SettingUtilsTest.java
@@ -49,7 +49,7 @@ public class SettingUtilsTest {
             ImmutableList<ImmutableList<?>> originalValue = 
ImmutableList.of(ImmutableList.of(), ImmutableList.of(1));
             Object value = castSetting(
                     SETTING_NAME,
-                    originalValue, true,
+                    originalValue, DefaultValue.NULL,
                     List.class,
                     new ListItemType(List.class),
                     new ListItemType(Integer.class)
@@ -73,7 +73,7 @@ public class SettingUtilsTest {
 
     @Test
     public void testOptional() {
-        assertNull(castSetting(SETTING_NAME, null, true, Integer.class));
+        assertNull(castSetting(SETTING_NAME, null, DefaultValue.NULL, 
Integer.class));
         try {
             castSetting(SETTING_NAME, null, Integer.class);
             fail();

Reply via email to