From a24bfd8e6439a14568046dd646f427f1cf095fec Mon Sep 17 00:00:00 2001
From: Mark Craig <mark.craig@forgerock.com>
Date: Tue, 31 Mar 2015 14:18:17 +0000
Subject: [PATCH] CR-6519 OPENDJ-1899 Generate multiple man pages for dsconfig

---
 opendj-cli/src/main/resources/templates/dscfgReference.ftl                               |   46 ++++++
 opendj-maven-plugin/src/main/java/org/forgerock/opendj/maven/GenerateRefEntriesMojo.java |   92 ++++++++++++
 opendj-cli/src/main/resources/templates/refSect1.ftl                                     |    4 
 opendj-cli/src/main/resources/templates/dscfgAppendProps.ftl                             |    4 
 opendj-cli/src/main/resources/templates/dscfgListItem.ftl                                |   36 +++++
 opendj-cli/src/main/resources/com/forgerock/opendj/cli/cli.properties                    |    2 
 opendj-cli/src/main/resources/templates/dscfgSubcommand.ftl                              |  106 +++++++++++++++
 opendj-server-legacy/src/main/docbkx/reference/index.xml                                 |    4 
 opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommandArgumentParser.java          |  115 +++++++++++++++-
 9 files changed, 397 insertions(+), 12 deletions(-)

diff --git a/opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommandArgumentParser.java b/opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommandArgumentParser.java
index 8134ec6..99f6e03 100644
--- a/opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommandArgumentParser.java
+++ b/opendj-cli/src/main/java/com/forgerock/opendj/cli/SubCommandArgumentParser.java
@@ -396,7 +396,7 @@
     @Override
     public void setUsageArgument(Argument argument, OutputStream outputStream) {
         super.setUsageArgument(argument, outputStream);
-        usageGroupArguments.put(argument, Collections.<SubCommand> emptySet());
+        usageGroupArguments.put(argument, Collections.<SubCommand>emptySet());
     }
 
     /**
@@ -1127,7 +1127,11 @@
     }
 
     /**
-     * Appends a generated DocBook XML RefEntry (man page) to the StringBuilder.
+     * Appends one or more generated DocBook XML RefEntry elements (man pages) to the StringBuilder.
+     * <br>
+     * If the result contains more than one RefEntry,
+     * then the RefEntry elements are separated with a marker:
+     * {@code @@@scriptName + "-" + subCommand.getName() + @@@}.
      *
      * @param builder       Append the RefEntry element to this.
      * @param subCommands   Collection of subcommands for this tool.
@@ -1155,6 +1159,11 @@
         map.put("subcommands", toRefSect1(scriptName, subCommands));
         map.put("trailingSectionString", System.getProperty("org.forgerock.opendj.gendoc.trailing"));
         applyTemplate(builder, "refEntry.ftl", map);
+
+        // For dsconfig, generate one page per subcommand.
+        if (scriptName.equals("dsconfig")) {
+            appendSubCommandPages(builder, scriptName, subCommands);
+        }
     }
 
     /**
@@ -1171,10 +1180,17 @@
         Map<String, Object> map = new HashMap<String, Object>();
         map.put("name", scriptName);
         map.put("info", getDocSubcommandsDescriptionSupplement());
-
+        if (scriptName.equals("dsconfig")) {
+            // Break dsconfig into multiple pages, so use only the list here.
+            map.put("isItemizedList", true);
+        }
         List<String> scUsageList = new ArrayList<String>();
         for (SubCommand subCommand : subCommands) {
-            scUsageList.add(toRefSect2(scriptName, subCommand));
+            if (scriptName.equals("dsconfig")) {
+                scUsageList.add(getSubCommandListItem(scriptName, subCommand));
+            } else {
+                scUsageList.add(toRefSect2(scriptName, subCommand));
+            }
         }
         map.put("subcommands", scUsageList);
 
@@ -1184,6 +1200,22 @@
     }
 
     /**
+     * Returns a DocBook XML ListItem element linking to the subcommand page.
+     * @param scriptName    The name of this script.
+     * @param subCommand    The SubCommand to reference.
+     * @return A DocBook XML ListItem element linking to the subcommand page.
+     */
+    private String getSubCommandListItem(String scriptName, SubCommand subCommand) {
+        Map<String, Object> map = new HashMap<String, Object>();
+        map.put("id", scriptName + "-" + subCommand.getName());
+        map.put("name", scriptName + " " + subCommand.getName());
+        map.put("description", subCommand.getDescription());
+        StringBuilder sb = new StringBuilder();
+        applyTemplate(sb, "dscfgListItem.ftl", map);
+        return sb.toString();
+    }
+
+    /**
      * Returns a generated DocBook XML RefSect2 element for a single subcommand to the StringBuilder.
      *
      * @param scriptName    The name of this script.
@@ -1203,6 +1235,22 @@
         // If there is a supplement to the description for this subcommand,
         // then it is already DocBook XML, so use it as is.
         map.put("info", subCommand.getDocDescriptionSupplement());
+        setSubCommandOptionsInfo(map, subCommand);
+
+        StringBuilder sb = new StringBuilder();
+        applyTemplate(sb, "refSect2.ftl", map);
+        return sb.toString();
+    }
+
+    /**
+     * Sets information for the subcommand options in the map.
+     * <br>
+     * The map is expected to be used in a FreeMarker template to generate docs.
+     *
+     * @param map           The map in which to set the information.
+     * @param subCommand    The subcommand containing the information.
+     */
+    private void setSubCommandOptionsInfo(Map<String, Object> map, SubCommand subCommand) {
         if (!subCommand.getArguments().isEmpty()) {
             List<Map<String, Object>> options = new LinkedList<Map<String, Object>>();
             String nameOption = null;
@@ -1236,9 +1284,62 @@
         if (subCommandUsageHandler != null) {
             map.put("propertiesInfo", subCommandUsageHandler.getProperties(subCommand));
         }
+    }
 
-        StringBuilder sb = new StringBuilder();
-        applyTemplate(sb, "refSect2.ftl", map);
-        return sb.toString();
+    /**
+     * Appends a generated DocBook XML RefEntry element for each subcommand to the StringBuilder.
+     * <br>
+     * The RefEntry elements are separated with a marker:
+     * {@code @@@scriptName + "-" + subCommand.getName() + @@@}.
+     *
+     * @param builder       Append the RefEntry elements to this.
+     * @param scriptName    The name of the tool with subcommands.
+     * @param subCommands   SubCommands containing reference information.
+     */
+    private void appendSubCommandPages(StringBuilder builder, String scriptName, Collection<SubCommand> subCommands) {
+        for (SubCommand subCommand : subCommands) {
+            Map<String, Object> map = new HashMap<String, Object>();
+            map.put("marker", "@@@" + scriptName + "-" + subCommand.getName() + "@@@");
+            map.put("locale", Locale.getDefault().getLanguage());
+            map.put("year", new SimpleDateFormat("yyyy").format(new Date()));
+            map.put("id", scriptName + "-" + subCommand.getName());
+            map.put("name", scriptName + " " + subCommand.getName());
+            map.put("purpose", subCommand.getDescription());
+            map.put("args", INFO_SUBCMDPARSER_OPTIONS.get());
+            map.put("descTitle", REF_TITLE_DESCRIPTION.get());
+            map.put("description", subCommand.getDescription());
+            map.put("info", subCommand.getDocDescriptionSupplement());
+            map.put("optionsTitle", REF_TITLE_OPTIONS.get());
+            map.put("optionsIntro", REF_INTRO_OPTIONS.get(scriptName + " " + subCommand.getName()));
+            setSubCommandOptionsInfo(map, subCommand);
+            applyTemplate(builder, "dscfgSubcommand.ftl", map);
+        }
+        appendSubCommandReference(builder, scriptName, subCommands);
+    }
+
+    /**
+     * Appends a generated DocBook XML Reference element XIncluding subcommands.
+     *
+     * @param builder       Append the Reference element to this.
+     * @param scriptName    The name of the tool with subcommands.
+     * @param subCommands   SubCommands containing reference information.
+     */
+    private void appendSubCommandReference(StringBuilder builder,
+                                           String scriptName,
+                                           Collection<SubCommand> subCommands) {
+        Map<String, Object> map = new HashMap<String, Object>();
+        map.put("marker", "@@@" + scriptName + "-subcommands-ref" + "@@@");
+        map.put("name", scriptName);
+        map.put("locale", Locale.getDefault().getLanguage());
+        map.put("title", REF_PART_TITLE_SUBCOMMANDS.get(scriptName));
+        map.put("partintro", REF_PART_INTRO_SUBCOMMANDS.get(scriptName));
+        List<Map<String, Object>> commands = new LinkedList<Map<String, Object>>();
+        for (SubCommand subCommand : subCommands) {
+            Map<String, Object> scMap = new HashMap<String, Object>();
+            scMap.put("id", scriptName + "-" + subCommand.getName());
+            commands.add(scMap);
+        }
+        map.put("subcommands", commands);
+        applyTemplate(builder, "dscfgReference.ftl", map);
     }
 }
diff --git a/opendj-cli/src/main/resources/com/forgerock/opendj/cli/cli.properties b/opendj-cli/src/main/resources/com/forgerock/opendj/cli/cli.properties
index 444d981..404a5f4 100755
--- a/opendj-cli/src/main/resources/com/forgerock/opendj/cli/cli.properties
+++ b/opendj-cli/src/main/resources/com/forgerock/opendj/cli/cli.properties
@@ -976,6 +976,8 @@
 REF_INTRO_OPTIONS=The <command>%s</command> command takes the following options:
 REF_DEFAULT=Default: %s
 REF_TITLE_SUBCOMMANDS=Subcommands
+REF_PART_TITLE_SUBCOMMANDS=%s Subcommands Reference
+REF_PART_INTRO_SUBCOMMANDS=This section covers <command>%s</command> subcommands.
 REF_SHORT_DESC_UNINSTALL=remove OpenDJ directory server software
 
 # Supplements to descriptions for generated reference documentation.
diff --git a/opendj-cli/src/main/resources/templates/dscfgAppendProps.ftl b/opendj-cli/src/main/resources/templates/dscfgAppendProps.ftl
index 790d19a..da914fa 100644
--- a/opendj-cli/src/main/resources/templates/dscfgAppendProps.ftl
+++ b/opendj-cli/src/main/resources/templates/dscfgAppendProps.ftl
@@ -24,7 +24,7 @@
  #      Copyright 2015 ForgeRock AS.
  #
  #-->
-<refsect3 xml:id="${id}">
+<refsect1 xml:id="${id}">
   <title>${title}</title>
 
   <para>
@@ -32,4 +32,4 @@
   </para>
 
   ${list}
-</refsect3>
+</refsect1>
diff --git a/opendj-cli/src/main/resources/templates/dscfgListItem.ftl b/opendj-cli/src/main/resources/templates/dscfgListItem.ftl
new file mode 100644
index 0000000..9b49a1c
--- /dev/null
+++ b/opendj-cli/src/main/resources/templates/dscfgListItem.ftl
@@ -0,0 +1,36 @@
+<#--
+ # CDDL HEADER START
+ #
+ # The contents of this file are subject to the terms of the
+ # Common Development and Distribution License, Version 1.0 only
+ # (the "License").  You may not use this file except in compliance
+ # with the License.
+ #
+ # You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ # or http://forgerock.org/license/CDDLv1.0.html.
+ # See the License for the specific language governing permissions
+ # and limitations under the License.
+ #
+ # When distributing Covered Code, include this CDDL HEADER in each
+ # file and include the License file at legal-notices/CDDLv1_0.txt.
+ # If applicable, add the following below this CDDL HEADER,
+ # with the fields enclosed by brackets "[]" replaced
+ # with your own identifying information:
+ #
+ #      Portions Copyright [yyyy] [name of copyright owner]
+ #
+ # CDDL HEADER END
+ #
+ #      Copyright 2015 ForgeRock AS.
+ #
+ #-->
+<listitem>
+ <para>
+  <link
+   <#-- Link to the Reference. Change this if the pages move to another document. -->
+   xlink:href="reference#${id}"
+   xlink:role="http://docbook.org/xlink/role/olink"
+   xlink:show="new"
+  ><command>${name}</command></link>: ${description}
+ </para>
+</listitem>
diff --git a/opendj-cli/src/main/resources/templates/dscfgReference.ftl b/opendj-cli/src/main/resources/templates/dscfgReference.ftl
new file mode 100644
index 0000000..1e3bcad
--- /dev/null
+++ b/opendj-cli/src/main/resources/templates/dscfgReference.ftl
@@ -0,0 +1,46 @@
+<#--
+ # CDDL HEADER START
+ #
+ # The contents of this file are subject to the terms of the
+ # Common Development and Distribution License, Version 1.0 only
+ # (the "License").  You may not use this file except in compliance
+ # with the License.
+ #
+ # You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ # or http://forgerock.org/license/CDDLv1.0.html.
+ # See the License for the specific language governing permissions
+ # and limitations under the License.
+ #
+ # When distributing Covered Code, include this CDDL HEADER in each
+ # file and include the License file at legal-notices/CDDLv1_0.txt.
+ # If applicable, add the following below this CDDL HEADER,
+ # with the fields enclosed by brackets "[]" replaced
+ # with your own identifying information:
+ #
+ #      Portions Copyright [yyyy] [name of copyright owner]
+ #
+ # CDDL HEADER END
+ #
+ #      Copyright 2015 ForgeRock AS.
+ #
+ #-->
+${marker}
+<reference xml:id="${name}-subcommands-ref"
+           xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="${locale}"
+           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+           xsi:schemaLocation="http://docbook.org/ns/docbook
+                               http://docbook.org/xml/5.0/xsd/docbook.xsd"
+           xmlns:xinclude="http://www.w3.org/2001/XInclude">
+
+ <title>${title}</title>
+
+ <partintro>
+  <para>
+   ${partintro}
+  </para>
+ </partintro>
+
+ <#list subcommands as subcommand>
+ <xinclude:include href="../man-pages/man-${subcommand.id}.xml" />
+ </#list>
+</reference>
diff --git a/opendj-cli/src/main/resources/templates/dscfgSubcommand.ftl b/opendj-cli/src/main/resources/templates/dscfgSubcommand.ftl
new file mode 100644
index 0000000..ad7f243
--- /dev/null
+++ b/opendj-cli/src/main/resources/templates/dscfgSubcommand.ftl
@@ -0,0 +1,106 @@
+${marker}
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ! CCPL HEADER START
+  !
+  ! This work is licensed under the Creative Commons
+  ! Attribution-NonCommercial-NoDerivs 3.0 Unported License.
+  ! To view a copy of this license, visit
+  ! http://creativecommons.org/licenses/by-nc-nd/3.0/
+  ! or send a letter to Creative Commons, 444 Castro Street,
+  ! Suite 900, Mountain View, California, 94041, USA.
+  !
+  ! You can also obtain a copy of the license at
+  ! trunk/opendj/legal-notices/CC-BY-NC-ND.txt.
+  ! See the License for the specific language governing permissions
+  ! and limitations under the License.
+  !
+  ! If applicable, add the following below this CCPL HEADER, with the fields
+  ! enclosed by brackets "[]" replaced with your own identifying information:
+  !      Portions Copyright [yyyy] [name of copyright owner]
+  !
+  ! CCPL HEADER END
+  !
+  !      Copyright 2011-${year} ForgeRock AS.
+  !
+-->
+<refentry xml:id="${id}"
+          xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="${locale}"
+          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+          xsi:schemaLocation="http://docbook.org/ns/docbook
+                              http://docbook.org/xml/5.0/xsd/docbook.xsd"
+          xmlns:xlink="http://www.w3.org/1999/xlink"
+          xmlns:xinclude="http://www.w3.org/2001/XInclude">
+
+ <info>
+  <copyright>
+   <year>2011-${year}</year>
+   <holder>ForgeRock AS.</holder>
+  </copyright>
+ </info>
+
+ <refmeta>
+  <refentrytitle>${name}</refentrytitle><manvolnum>1</manvolnum>
+  <refmiscinfo class="software">OpenDJ</refmiscinfo>
+  <refmiscinfo class="version">${r"${project.version}"}</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+  <refname>${name}</refname>
+  <refpurpose>${purpose}</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+  <cmdsynopsis>
+   <command>${name}</command>
+   <arg choice="plain">${args}</arg>
+  </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1 xml:id="${id}-description">
+  <title>${descTitle}</title>
+
+  <para>
+   ${description}
+  </para>
+
+  <#if info??>${info}</#if>
+ </refsect1>
+
+ <#if options??>
+ <refsect1 xml:id="${id}-options">
+  <title>${optionsTitle}</title>
+
+  <variablelist>
+   <para>
+    ${optionsIntro}
+   </para>
+
+   <#list options as option>
+   <varlistentry>
+    <term><option>${option.synopsis?xml}</option></term>
+    <listitem>
+     <para>
+      ${option.description}
+     </para>
+
+     <#if option.info??>
+       <#if option.info.usage??>${option.info.usage}</#if>
+
+       <#if option.info.default??>
+       <para>
+        ${option.info.default}
+       </para>
+       </#if>
+
+       <#if option.info.doc??>${option.info.doc}</#if>
+     </#if>
+    </listitem>
+   </varlistentry>
+   </#list>
+  </variablelist>
+ </refsect1>
+ </#if>
+
+ <#if propertiesInfo??>${propertiesInfo}</#if>
+</refentry>
diff --git a/opendj-cli/src/main/resources/templates/refSect1.ftl b/opendj-cli/src/main/resources/templates/refSect1.ftl
index c4e71fe..1302f58 100644
--- a/opendj-cli/src/main/resources/templates/refSect1.ftl
+++ b/opendj-cli/src/main/resources/templates/refSect1.ftl
@@ -35,7 +35,9 @@
    The <command>${name}</command> utility supports the following subcommands.
   </para>
 
+  <#if isItemizedList??><itemizedlist></#if>
   <#list subcommands as subcommand>
-    ${subcommand}
+   ${subcommand}
   </#list>
+  <#if isItemizedList??></itemizedlist></#if>
 </refsect1>
diff --git a/opendj-maven-plugin/src/main/java/org/forgerock/opendj/maven/GenerateRefEntriesMojo.java b/opendj-maven-plugin/src/main/java/org/forgerock/opendj/maven/GenerateRefEntriesMojo.java
index 580521c..5901008 100644
--- a/opendj-maven-plugin/src/main/java/org/forgerock/opendj/maven/GenerateRefEntriesMojo.java
+++ b/opendj-maven-plugin/src/main/java/org/forgerock/opendj/maven/GenerateRefEntriesMojo.java
@@ -34,9 +34,14 @@
 import org.apache.maven.plugins.annotations.Parameter;
 import org.apache.maven.plugins.annotations.ResolutionScope;
 import org.apache.maven.project.MavenProject;
+import org.forgerock.util.Utils;
 
 import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
 import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.IOException;
 import java.io.InputStream;
@@ -45,10 +50,14 @@
 import java.net.URISyntaxException;
 import java.net.URL;
 import java.net.URLClassLoader;
+import java.nio.channels.FileChannel;
+import java.nio.charset.Charset;
 import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Generate DocBook RefEntry source documents for command-line tools man pages.
@@ -165,6 +174,14 @@
         } catch (IOException e) {
             throw new MojoExecutionException(toolClass + " not found", e);
         }
+
+        if (tool.getName().equals("dsconfig")) {
+            try {
+                splitPage(manPage);
+            } catch (IOException e) {
+                throw new MojoExecutionException("Failed to split "  + manPage.getName(), e);
+            }
+        }
     }
 
     /**
@@ -287,9 +304,80 @@
                 writer.write(EOL);
             }
         } finally {
-            if (writer != null) {
-                writer.close();
+            Utils.closeSilently(writer);
+        }
+    }
+
+    /**
+     * Splits the content of a single man page into multiple pages.
+     * <br>
+     * RefEntry elements must be separated with a marker:
+     * {@code @@@scriptName + "-" + subCommand.getName() + @@@}.
+     *
+     * @param page          The page to split.
+     * @throws IOException  Failed to split the page.
+     */
+    private void splitPage(final File page) throws IOException {
+        // Read from a copy of the page.
+        final File pageCopy = new File(page.getPath() + ".tmp");
+        copyFile(page, pageCopy);
+        final BufferedReader reader = new BufferedReader(new FileReader(pageCopy));
+        try {
+            // Write first to the page, then to pages named according to marker values.
+            File output = page;
+            getLog().info("Rewriting man page: " + page.getPath());
+            final Pattern marker = Pattern.compile("@@@(.+?)@@@");
+            final StringBuilder builder = new StringBuilder();
+            String line;
+            while ((line = reader.readLine()) != null) {
+                final Matcher matcher = marker.matcher(line);
+                if (matcher.find()) {
+                    writeToFile(builder.toString(), output);
+                    builder.setLength(0);
+                    output = new File(page.getParentFile(), "man-" + matcher.group(1) + ".xml");
+                    getLog().info("Writing man page: " + output.getPath());
+                } else {
+                    builder.append(line).append(System.getProperty("line.separator"));
+                }
             }
+            writeToFile(builder.toString(), output);
+            if (!pageCopy.delete()) {
+                throw new IOException("Failed to delete " +  pageCopy.getName());
+            }
+        } finally {
+            Utils.closeSilently(reader);
+        }
+    }
+
+    /**
+     * Writes the content of the input to the output file.
+     * @param input         The UTF-8 input to write.
+     * @param output        The file to write it to.
+     * @throws IOException  Failed to write the content of the input.
+     */
+    private void writeToFile(final String input, final File output) throws IOException {
+        InputStream is = new ByteArrayInputStream(input.getBytes(Charset.forName("UTF-8")));
+        writeToFile(is, output);
+    }
+
+    /**
+     * Copies the content of the original file to the copy.
+     * @param original      The original file.
+     * @param copy          The copy.
+     * @throws IOException  Failed to make the copy.
+     */
+    private void copyFile(File original, File copy) throws IOException {
+        if (!copy.exists() && !copy.createNewFile()) {
+            throw new IOException("Failed to create " + copy);
+        }
+        FileChannel in  = null;
+        FileChannel out = null;
+        try {
+            in  = new FileInputStream(original).getChannel();
+            out = new FileOutputStream(copy).getChannel();
+            out.transferFrom(in, 0, in.size());
+        } finally {
+            Utils.closeSilently(in, out);
         }
     }
 }
diff --git a/opendj-server-legacy/src/main/docbkx/reference/index.xml b/opendj-server-legacy/src/main/docbkx/reference/index.xml
index e2fcc1b..97e733c 100644
--- a/opendj-server-legacy/src/main/docbkx/reference/index.xml
+++ b/opendj-server-legacy/src/main/docbkx/reference/index.xml
@@ -187,6 +187,10 @@
   <xinclude:include href='../man-pages/man-windows-service.xml' />
  </reference>
 
+ <xinclude:include href="../man-pages/man-dsconfig-subcommands-ref.xml">
+  <xinclude:fallback><!-- Failed to include page --></xinclude:fallback>
+ </xinclude:include>
+
  <xinclude:include href="../shared/glossary.xml" />
 
  <xinclude:include href="appendix-rest2ldap.xml" />

--
Gitblit v1.10.0