You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kudu.apache.org by ad...@apache.org on 2017/04/04 01:02:50 UTC

kudu git commit: KUDU-1708. Document Kudu command-line tools

Repository: kudu
Updated Branches:
  refs/heads/master 7206041f9 -> 2407c2cb1


KUDU-1708. Document Kudu command-line tools

- Adds helpxml flag support to the kudu binary
- Adds doc script and xslt to convert xml to asciidoc

Change-Id: I9f484f772cbaeb385687d83a2665ae4d7292aaf5
Reviewed-on: http://gerrit.cloudera.org:8080/6525
Reviewed-by: Adar Dembo <ad...@cloudera.com>
Tested-by: Adar Dembo <ad...@cloudera.com>


Project: http://git-wip-us.apache.org/repos/asf/kudu/repo
Commit: http://git-wip-us.apache.org/repos/asf/kudu/commit/2407c2cb
Tree: http://git-wip-us.apache.org/repos/asf/kudu/tree/2407c2cb
Diff: http://git-wip-us.apache.org/repos/asf/kudu/diff/2407c2cb

Branch: refs/heads/master
Commit: 2407c2cb1e413c705ceb2b9479fe74d9cc432883
Parents: 7206041
Author: Grant Henke <gr...@gmail.com>
Authored: Thu Mar 30 22:43:32 2017 -0500
Committer: Adar Dembo <ad...@cloudera.com>
Committed: Tue Apr 4 01:02:23 2017 +0000

----------------------------------------------------------------------
 docs/command_line_tools_reference.adoc          |  36 +++++
 docs/support/jekyll-templates/document.html.erb |   1 +
 docs/support/scripts/make_docs.sh               |  24 ++++
 docs/support/xsl/tool_to_asciidoc.xsl           | 142 +++++++++++++++++++
 src/kudu/tools/CMakeLists.txt                   |   3 +-
 src/kudu/tools/kudu-tool-test.cc                |  35 +++++
 src/kudu/tools/tool_action-test.cc              | 117 +++++++++++++++
 src/kudu/tools/tool_action.cc                   |  97 ++++++++++++-
 src/kudu/tools/tool_action.h                    |   6 +
 src/kudu/tools/tool_main.cc                     |  59 +++++---
 10 files changed, 496 insertions(+), 24 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/kudu/blob/2407c2cb/docs/command_line_tools_reference.adoc
----------------------------------------------------------------------
diff --git a/docs/command_line_tools_reference.adoc b/docs/command_line_tools_reference.adoc
new file mode 100644
index 0000000..5456d6e
--- /dev/null
+++ b/docs/command_line_tools_reference.adoc
@@ -0,0 +1,36 @@
+// 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.
+
+[[command_line_tools_reference]]
+= Apache Kudu Command Line Tools Reference
+
+:author: Kudu Team
+:imagesdir: ./images
+:icons: font
+:toc: left
+:toclevels: 2
+:doctype: book
+:backend: html5
+:sectlinks:
+:experimental:
+
+// The contents of this file are generated from the output of the `--helpxml`
+// flag for the kudu tool, during the build of the documentation.
+// Do not edit this file or the included files manually.
+
+// This gets replaced by the script that builds the docs
+@@TOOLS_REFERENCE@@
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/kudu/blob/2407c2cb/docs/support/jekyll-templates/document.html.erb
----------------------------------------------------------------------
diff --git a/docs/support/jekyll-templates/document.html.erb b/docs/support/jekyll-templates/document.html.erb
index 7d18c04..a96b68a 100644
--- a/docs/support/jekyll-templates/document.html.erb
+++ b/docs/support/jekyll-templates/document.html.erb
@@ -97,6 +97,7 @@ end %>
         :transaction_semantics, "Kudu Transaction Semantics",
         :contributing, "Contributing to Kudu",
         :configuration_reference, "Kudu Configuration Reference",
+        :command_line_tools_reference, "Kudu Command Line Tools Reference",
         :known_issues, "Known Issues and Limitations",
         :export_control, "Export Control Notice"
       ]

http://git-wip-us.apache.org/repos/asf/kudu/blob/2407c2cb/docs/support/scripts/make_docs.sh
----------------------------------------------------------------------
diff --git a/docs/support/scripts/make_docs.sh b/docs/support/scripts/make_docs.sh
index dcbc1f8..c449c5a 100755
--- a/docs/support/scripts/make_docs.sh
+++ b/docs/support/scripts/make_docs.sh
@@ -167,6 +167,30 @@ cp $SOURCE_ROOT/docs/configuration_reference* $GEN_DOC_DIR/
 sed -i "s#@@CONFIGURATION_REFERENCE@@#${INCLUSIONS_SUPPORTED}#" ${GEN_DOC_DIR}/configuration_reference.adoc
 sed -i "s#@@CONFIGURATION_REFERENCE@@#${INCLUSIONS_UNSUPPORTED}#" ${GEN_DOC_DIR}/configuration_reference_unsupported.adoc
 
+# Create tool references
+echo "Running kudu --helpxml"
+(
+  # Reset environment to avoid affecting the default flag values.
+  for var in $(env | awk -F= '{print $1}' | egrep -i 'KUDU|GLOG'); do
+    echo "unset $var"
+    eval "unset $var"
+  done
+
+  # Create the XML file.
+  # This command exits with a nonzero value.
+  $BUILD_ROOT/bin/kudu --helpxml > ${GEN_DOC_DIR}/kudu.xml || true
+)
+
+# Create the supported config reference
+xsltproc \
+-o $GEN_DOC_DIR/command_line_tools.adoc \
+  $SOURCE_ROOT/docs/support/xsl/tool_to_asciidoc.xsl \
+${GEN_DOC_DIR}/kudu.xml
+
+# Add the includes to the cli tools reference files, replacing the template lines
+cp $SOURCE_ROOT/docs/command_line_tools_reference.adoc $GEN_DOC_DIR/
+sed -i "s#@@TOOLS_REFERENCE@@#include::command_line_tools.adoc[leveloffset=+1]\n#" ${GEN_DOC_DIR}/command_line_tools_reference.adoc
+
 # If we're generating the web site, pass the template which causes us
 # to generate Jekyll templates instead of full HTML.
 if [ -n "$SITE" ]; then

http://git-wip-us.apache.org/repos/asf/kudu/blob/2407c2cb/docs/support/xsl/tool_to_asciidoc.xsl
----------------------------------------------------------------------
diff --git a/docs/support/xsl/tool_to_asciidoc.xsl b/docs/support/xsl/tool_to_asciidoc.xsl
new file mode 100644
index 0000000..1d36cf3
--- /dev/null
+++ b/docs/support/xsl/tool_to_asciidoc.xsl
@@ -0,0 +1,142 @@
+<?xml version="1.0"?>
+<!--
+
+Licensed 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.
+-->
+
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+<xsl:output method="text"/>
+
+<!-- Normalize space -->
+<xsl:template match="text()">
+    <xsl:if test="normalize-space(.)">
+      <xsl:value-of select="normalize-space(.)"/>
+    </xsl:if>
+</xsl:template>
+
+<xsl:template match="AllModes">
+<!-- Inject the license text into the header -->
+////
+//
+// 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.
+////
+
+:author: Kudu Team
+:imagesdir: ./images
+:icons: font
+:toc: left
+:toclevels: 1
+:doctype: book
+:backend: html5
+:sectlinks:
+:experimental:
+
+= Command Hierarchy
+<xsl:apply-templates select="mode" mode="toc"/>
+
+= Command Details
+<xsl:apply-templates select="mode" mode="content"/>
+
+</xsl:template>
+
+<!-- Table of contents template -->
+<xsl:template match="mode|action" mode="toc">
+<xsl:variable name="depth" select="count(ancestor::*)"/>
+<xsl:variable name="ref"><xsl:if test="../name"><xsl:value-of select="../name"/>-</xsl:if><xsl:value-of select="name"/></xsl:variable>
+<!-- Print bullets at mode/action depth -->
+<xsl:for-each select="(//node())[$depth >= position()]">*</xsl:for-each> &lt;&lt;<xsl:value-of select="$ref"/>,<xsl:value-of select="name"/>&gt;&gt;
+<xsl:apply-templates select="mode|action" mode="toc"/>
+</xsl:template>
+
+<!-- Content for mode template -->
+<xsl:template match="mode" mode="content">
+<xsl:variable name="depth" select="count(ancestor::*) + 1"/>
+<!-- Create unique anchor with the parent name and name -->
+<xsl:variable name="anchor"><xsl:if test="../name"><xsl:value-of select="../name"/>-</xsl:if><xsl:value-of select="name"/></xsl:variable>
+[[<xsl:value-of select="$anchor"/>]]
+<!-- Print header level at mode depth -->
+<xsl:for-each select="(//node())[$depth >= position()]">=</xsl:for-each> `<xsl:value-of select="name"/>`: <xsl:value-of select="description"/>
+{empty} +
+<xsl:apply-templates select="mode|action" mode="content"/>
+</xsl:template>
+
+<!-- Content for action template -->
+<xsl:template match="action" mode="content">
+<xsl:variable name="depth" select="count(ancestor::*) + 1"/>
+<!-- Create unique anchor with the parent name and name -->
+<xsl:variable name="anchor"><xsl:if test="../name"><xsl:value-of select="../name"/>-</xsl:if><xsl:value-of select="name"/></xsl:variable>
+[[<xsl:value-of select="$anchor"/>]]
+<!-- Print header level at action depth -->
+<xsl:for-each select="(//node())[$depth >= position()]">=</xsl:for-each> `<xsl:value-of select="name"/>`: <xsl:value-of select="description"/>{nbsp}
+<xsl:if test="extra_description != ''"><xsl:value-of select="extra_description"/> +</xsl:if>
+*Usage:* +
+`<xsl:value-of select="usage"/>`
+{empty} +
+*Arguments:*
+[frame="topbot",options="header"]
+|===
+| Name |  Description | Type | Default
+<xsl:for-each select="argument">
+<!-- escape pipe character in description for use in tables -->
+<xsl:variable name="escaped_description">
+  <xsl:call-template name="string-replace-all">
+    <xsl:with-param name="text" select="description" />
+    <xsl:with-param name="replace" select="'|'" />
+    <xsl:with-param name="by" select="'\|'" />
+  </xsl:call-template>
+</xsl:variable>
+| <xsl:value-of select="name"/><xsl:if test="contains(kind, 'variadic')">...</xsl:if><xsl:if test="contains(kind, 'optional')"> (optional)</xsl:if>
+| <xsl:value-of select="$escaped_description"/>
+| <xsl:value-of select="type"/>
+| <xsl:choose><xsl:when test="default_value != ''">`<xsl:value-of select="default_value"/>`</xsl:when><xsl:otherwise>none</xsl:otherwise></xsl:choose>
+</xsl:for-each>
+|===
+{empty} +
+</xsl:template>
+
+<!-- Template to support string replacement in XSLT 1.0) -->
+<xsl:template name="string-replace-all">
+<xsl:param name="text" />
+<xsl:param name="replace" />
+<xsl:param name="by" />
+<xsl:choose>
+  <xsl:when test="contains($text, $replace)">
+    <xsl:value-of select="substring-before($text,$replace)" />
+    <xsl:value-of select="$by" />
+    <xsl:call-template name="string-replace-all">
+      <xsl:with-param name="text" select="substring-after($text,$replace)" />
+      <xsl:with-param name="replace" select="$replace" />
+      <xsl:with-param name="by" select="$by" />
+    </xsl:call-template>
+  </xsl:when>
+  <xsl:otherwise>
+    <xsl:value-of select="$text" />
+  </xsl:otherwise>
+</xsl:choose>
+</xsl:template>
+</xsl:stylesheet>

http://git-wip-us.apache.org/repos/asf/kudu/blob/2407c2cb/src/kudu/tools/CMakeLists.txt
----------------------------------------------------------------------
diff --git a/src/kudu/tools/CMakeLists.txt b/src/kudu/tools/CMakeLists.txt
index c38d2da..0f75fca 100644
--- a/src/kudu/tools/CMakeLists.txt
+++ b/src/kudu/tools/CMakeLists.txt
@@ -32,6 +32,7 @@ set(LINK_LIBS
 add_library(kudu_tools_util
   color.cc
   data_gen_util.cc
+  tool_action.cc
   tool_action_common.cc
 )
 target_link_libraries(kudu_tools_util
@@ -56,7 +57,6 @@ target_link_libraries(ksck
 )
 
 add_executable(kudu
-  tool_action.cc
   tool_action_cluster.cc
   tool_action_fs.cc
   tool_action_local_replica.cc
@@ -112,3 +112,4 @@ ADD_KUDU_TEST_DEPENDENCIES(kudu-tool-test
 ADD_KUDU_TEST(kudu-ts-cli-test)
 ADD_KUDU_TEST_DEPENDENCIES(kudu-ts-cli-test
   kudu)
+ADD_KUDU_TEST(tool_action-test)

http://git-wip-us.apache.org/repos/asf/kudu/blob/2407c2cb/src/kudu/tools/kudu-tool-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/kudu-tool-test.cc b/src/kudu/tools/kudu-tool-test.cc
index 68c4493..e082cf9 100644
--- a/src/kudu/tools/kudu-tool-test.cc
+++ b/src/kudu/tools/kudu-tool-test.cc
@@ -277,6 +277,41 @@ void ToolTest::StartMiniCluster(int num_masters,
   ASSERT_OK(mini_cluster_->Start());
 }
 
+TEST_F(ToolTest, TestHelpXML) {
+  string stdout;
+  string stderr;
+  Status s = RunTool("--helpxml", &stdout, &stderr, nullptr, nullptr);
+
+  ASSERT_TRUE(s.IsRuntimeError());
+  ASSERT_FALSE(stdout.empty());
+  ASSERT_TRUE(stderr.empty());
+
+  // All wrapped in AllModes node
+  ASSERT_STR_MATCHES(stdout, "<\\?xml version=\"1.0\"\\?><AllModes>.*</AllModes>");
+
+  // Verify all modes are output
+  const vector<string> modes = {
+      "cluster",
+      "fs",
+      "local_replica",
+      "master",
+      "pbc",
+      "remote_replica",
+      "table",
+      "tablet",
+      "test",
+      "tserver",
+      "wal",
+      "dump",
+      "cmeta",
+      "change_config"
+  };
+
+  for (const auto& mode : modes) {
+    ASSERT_STR_MATCHES(stdout, Substitute(".*<mode><name>$0</name>.*</mode>.*", mode));
+  }
+}
+
 TEST_F(ToolTest, TestTopLevelHelp) {
   const vector<string> kTopLevelRegexes = {
       "cluster.*Kudu cluster",

http://git-wip-us.apache.org/repos/asf/kudu/blob/2407c2cb/src/kudu/tools/tool_action-test.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_action-test.cc b/src/kudu/tools/tool_action-test.cc
new file mode 100644
index 0000000..76c2255
--- /dev/null
+++ b/src/kudu/tools/tool_action-test.cc
@@ -0,0 +1,117 @@
+// 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.
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <gflags/gflags.h>
+#include <gtest/gtest.h>
+
+#include "kudu/tools/tool_action.h"
+
+// gflags for optional action parameters
+DEFINE_bool(opt_bool, false, "obd");
+DEFINE_string(opt_string, "", "osd");
+
+namespace kudu {
+namespace tools {
+
+using std::string;
+using std::stringstream;
+using std::unique_ptr;
+using std::vector;
+
+TEST(ToolActionTest, TestActionBuildHelpXML) {
+  unique_ptr<Action> action =
+      ActionBuilder("sample", nullptr)
+          .Description("d")
+          .ExtraDescription("ed")
+          .AddRequiredParameter({ "required", "rpd" })
+          .AddRequiredVariadicParameter({ "variadic", "vpd" })
+          .AddOptionalParameter("opt_string")
+          .AddOptionalParameter("opt_bool")
+          .Build();
+
+  string xml = action->BuildHelpXML(vector<Mode*>());
+  SCOPED_TRACE(xml);
+
+  stringstream ss;
+  ss << "<action>";
+  ss << "<name>sample</name>";
+  ss << "<description>d</description>";
+  ss << "<extra_description>ed</extra_description>";
+  ss << "<argument><kind>required</kind><name>required</name>"
+        "<description>rpd</description><type>string</type></argument>";
+  ss << "<argument><kind>variadic</kind><name>variadic</name>"
+        "<description>vpd</description><type>string</type></argument>";
+  ss << "<argument><kind>optional</kind><name>opt_string</name>"
+        "<description>osd</description><type>string</type>"
+        "<default_value></default_value></argument>";
+  ss << "<argument><kind>optional</kind><name>opt_bool</name>"
+        "<description>obd</description><type>bool</type>"
+        "<default_value>false</default_value></argument>";
+  ss << "<usage> sample &amp;lt;required&amp;gt; &amp;lt;variadic&amp;gt;... "
+        "[-opt_string=&amp;lt;string&amp;gt;] [-opt_bool]</usage>";
+  ss << "</action>";
+  string expected_xml = ss.str();
+
+  ASSERT_EQ(expected_xml, xml);
+}
+
+TEST(ToolActionTest, TestModeBuildHelpXML) {
+  unique_ptr<Action> action =
+      ActionBuilder("action", nullptr)
+          .Description("ad")
+          .AddRequiredParameter({ "required", "rpd" })
+          .Build();
+
+  unique_ptr<Mode> submode = ModeBuilder("submode")
+      .Description("subd")
+      .AddAction(std::move(action))
+      .Build();
+
+  unique_ptr<Mode> mode = ModeBuilder("mode")
+      .Description("md")
+      .AddMode(std::move(submode))
+      .Build();
+
+  vector<Mode*> chain = { mode.get() };
+
+  string xml = mode->BuildHelpXML(chain);
+  SCOPED_TRACE(xml);
+
+  stringstream ss;
+  ss << "<mode><name>mode</name>";
+  ss << "<description>md</description>";
+  ss << "<mode><name>submode</name>";
+  ss << "<description>subd</description>";
+  ss << "<action><name>action</name>";
+  ss << "<description>ad</description><extra_description></extra_description>";
+  ss << "<argument><kind>required</kind><name>required</name>"
+        "<description>rpd</description><type>string</type></argument>";
+  ss << "<usage>mode submode action &amp;lt;required&amp;gt;</usage>";
+  ss << "</action>";
+  ss << "</mode>";
+  ss << "</mode>";
+  string expected_xml = ss.str();
+
+  ASSERT_EQ(expected_xml, xml);
+}
+
+} // namespace tools
+} // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/2407c2cb/src/kudu/tools/tool_action.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_action.cc b/src/kudu/tools/tool_action.cc
index 1a920fc..a88849e 100644
--- a/src/kudu/tools/tool_action.cc
+++ b/src/kudu/tools/tool_action.cc
@@ -28,6 +28,7 @@
 #include "kudu/gutil/strings/join.h"
 #include "kudu/gutil/strings/split.h"
 #include "kudu/gutil/strings/substitute.h"
+#include "kudu/util/url-coding.h"
 
 using std::string;
 using std::unique_ptr;
@@ -61,8 +62,7 @@ string FakeDescribeOneFlag(const ActionArgsDescriptor::Arg& arg) {
 }
 
 string BuildUsageString(const vector<Mode*>& chain) {
-  string modes = JoinMapped(chain, [](Mode* a){ return a->name(); }, " ");
-  return Substitute("Usage: $0", modes);
+  return JoinMapped(chain, [](Mode* a){ return a->name(); }, " ");
 }
 
 
@@ -145,7 +145,8 @@ unique_ptr<Mode> ModeBuilder::Build() {
 // Get help for this mode, passing in its parent mode chain.
 string Mode::BuildHelp(const vector<Mode*>& chain) const {
   string msg;
-  msg += Substitute("$0 <command> [<args>]\n\n", BuildUsageString(chain));
+  msg += Substitute("Usage: $0 <command> [<args>]\n\n",
+                    BuildUsageString(chain));
   msg += "<command> can be one of the following:\n";
 
   vector<pair<string, string>> line_pairs;
@@ -170,6 +171,25 @@ string Mode::BuildHelp(const vector<Mode*>& chain) const {
   return msg;
 }
 
+string Mode::BuildHelpXML(const vector<Mode*>& chain) const {
+  string xml;
+  xml += "<mode>";
+  xml += Substitute("<name>$0</name>", name());
+  xml += Substitute("<description>$0</description>",
+                    EscapeForHtmlToString(description()));
+  for (const auto& a : actions()) {
+    xml += a->BuildHelpXML(chain);
+  }
+
+  for (const auto& m : modes()) {
+    vector<Mode*> m_chain(chain);
+    m_chain.push_back(m.get());
+    xml += m->BuildHelpXML(m_chain);
+  }
+  xml += "</mode>";
+  return xml;
+}
+
 ActionBuilder::ActionBuilder(const string& name, const ActionRunner& runner)
     : name_(name),
       runner_(runner) {
@@ -228,7 +248,7 @@ Status Action::Run(const vector<Mode*>& chain,
 }
 
 string Action::BuildHelp(const vector<Mode*>& chain) const {
-  string usage_msg = Substitute("$0 $1", BuildUsageString(chain), name());
+  string usage_msg = Substitute("Usage: $0 $1", BuildUsageString(chain), name());
   string desc_msg;
   for (const auto& param : args_.required) {
     usage_msg += Substitute(" <$0>", param.name);
@@ -278,5 +298,74 @@ string Action::BuildHelp(const vector<Mode*>& chain) const {
   return msg;
 }
 
+string Action::BuildHelpXML(const vector<Mode*>& chain) const {
+  string usage = Substitute("$0 $1", BuildUsageString(chain), name());
+  string xml;
+  xml += "<action>";
+  xml += Substitute("<name>$0</name>", name());
+  xml += Substitute("<description>$0</description>",
+                    EscapeForHtmlToString(description()));
+  xml += Substitute("<extra_description>$0</extra_description>",
+                    EscapeForHtmlToString(extra_description()
+                                              .get_value_or("")));
+  for (const auto& r : args().required) {
+    usage += Substitute(" &lt;$0&gt;", r.name);
+    xml += "<argument>";
+    xml += "<kind>required</kind>";
+    xml += Substitute("<name>$0</name>", r.name);
+    xml += Substitute("<description>$0</description>",
+                      EscapeForHtmlToString(r.description));
+    xml += "<type>string</type>";
+    xml += "</argument>";
+  }
+
+  if (args().variadic) {
+    const ActionArgsDescriptor::Arg& v = args().variadic.get();
+    usage += Substitute(" &lt;$0&gt;...", v.name);
+    xml += "<argument>";
+    xml += "<kind>variadic</kind>";
+    xml += Substitute("<name>$0</name>", v.name);
+    xml += Substitute("<description>$0</description>",
+                      EscapeForHtmlToString(v.description));
+    xml += "<type>string</type>";
+    xml += "</argument>";
+  }
+
+  for (const auto& o : args().optional) {
+    google::CommandLineFlagInfo gflag_info =
+        google::GetCommandLineFlagInfoOrDie(o.c_str());
+
+    if (gflag_info.type == "bool") {
+      if (gflag_info.default_value == "false") {
+        usage += Substitute(" [-$0]", o);
+      } else {
+        usage += Substitute(" [-no$0]", o);
+      }
+    } else {
+      string noun;
+      string::size_type last_underscore_idx = o.rfind('_');
+      if (last_underscore_idx != string::npos &&
+          last_underscore_idx != o.size() - 1) {
+        noun = o.substr(last_underscore_idx + 1);
+      } else {
+        noun = o;
+      }
+      usage += Substitute(" [-$0=&lt;$1&gt;]", o, noun);
+    }
+
+    xml += "<argument>";
+    xml += "<kind>optional</kind>";
+    xml += Substitute("<name>$0</name>", gflag_info.name);
+    xml += Substitute("<description>$0</description>", gflag_info.description);
+    xml += Substitute("<type>$0</type>", gflag_info.type);
+    xml += Substitute("<default_value>$0</default_value>",
+                      gflag_info.default_value);
+    xml += "</argument>";
+  }
+  xml += Substitute("<usage>$0</usage>", EscapeForHtmlToString(usage));
+  xml += "</action>";
+  return xml;
+}
+
 } // namespace tools
 } // namespace kudu

http://git-wip-us.apache.org/repos/asf/kudu/blob/2407c2cb/src/kudu/tools/tool_action.h
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_action.h b/src/kudu/tools/tool_action.h
index 082f0d0..522b895 100644
--- a/src/kudu/tools/tool_action.h
+++ b/src/kudu/tools/tool_action.h
@@ -107,6 +107,9 @@ class Mode {
   // Returns the help for this mode given its parent mode chain.
   std::string BuildHelp(const std::vector<Mode*>& chain) const;
 
+  // Returns the help xml for this mode and all child modes
+  std::string BuildHelpXML(const std::vector<Mode*>& chain) const;
+
   const std::string& name() const { return name_; }
 
   const std::string& description() const { return description_; }
@@ -242,6 +245,9 @@ class Action {
   // Returns the help for this action given its parent mode chain.
   std::string BuildHelp(const std::vector<Mode*>& chain) const;
 
+  // Returns the help xml for this action
+  std::string BuildHelpXML(const std::vector<Mode*>& chain) const;
+
   // Runs the operation represented by this action, given a parent mode chain
   // and marshaled command line arguments.
   Status Run(const std::vector<Mode*>& chain,

http://git-wip-us.apache.org/repos/asf/kudu/blob/2407c2cb/src/kudu/tools/tool_main.cc
----------------------------------------------------------------------
diff --git a/src/kudu/tools/tool_main.cc b/src/kudu/tools/tool_main.cc
index dc90db6..3aeb8f7 100644
--- a/src/kudu/tools/tool_main.cc
+++ b/src/kudu/tools/tool_main.cc
@@ -32,6 +32,7 @@
 #include "kudu/tools/tool_action.h"
 #include "kudu/util/flags.h"
 #include "kudu/util/logging.h"
+#include "kudu/util/path_util.h"
 #include "kudu/util/status.h"
 
 DECLARE_bool(help);
@@ -41,6 +42,7 @@ DECLARE_bool(helpxml);
 DECLARE_string(helpmatch);
 DECLARE_string(helpon);
 
+using std::cout;
 using std::cerr;
 using std::deque;
 using std::endl;
@@ -53,6 +55,23 @@ using strings::Substitute;
 namespace kudu {
 namespace tools {
 
+unique_ptr<Mode> RootMode(const string& name) {
+  return ModeBuilder(name)
+      .Description("Kudu Command Line Tools") // root mode description isn't printed
+      .AddMode(BuildClusterMode())
+      .AddMode(BuildFsMode())
+      .AddMode(BuildLocalReplicaMode())
+      .AddMode(BuildMasterMode())
+      .AddMode(BuildPbcMode())
+      .AddMode(BuildRemoteReplicaMode())
+      .AddMode(BuildTableMode())
+      .AddMode(BuildTabletMode())
+      .AddMode(BuildTestMode())
+      .AddMode(BuildTServerMode())
+      .AddMode(BuildWalMode())
+      .Build();
+}
+
 Status MarshalArgs(const vector<Mode*>& chain,
                    Action* action,
                    deque<string> input,
@@ -109,27 +128,24 @@ int DispatchCommand(const vector<Mode*>& chain,
 }
 
 // Replace hyphens with underscores in a string and return a copy.
-string HyphensToUnderscores(string str) {
+static string HyphensToUnderscores(string str) {
   std::replace(str.begin(), str.end(), '-', '_');
   return str;
 }
 
-int RunTool(int argc, char** argv, bool show_help) {
-  unique_ptr<Mode> root = ModeBuilder(argv[0])
-    .Description("doesn't matter") // root mode description isn't printed
-    .AddMode(BuildClusterMode())
-    .AddMode(BuildFsMode())
-    .AddMode(BuildLocalReplicaMode())
-    .AddMode(BuildMasterMode())
-    .AddMode(BuildPbcMode())
-    .AddMode(BuildRemoteReplicaMode())
-    .AddMode(BuildTableMode())
-    .AddMode(BuildTabletMode())
-    .AddMode(BuildTestMode())
-    .AddMode(BuildTServerMode())
-    .AddMode(BuildWalMode())
-    .Build();
+void DumpToolXML(const string& path) {
+  unique_ptr<Mode> root = RootMode(BaseName(path));
+  cout << "<?xml version=\"1.0\"?>";
+  cout << "<AllModes>";
+  for (const auto& mode : root->modes()) {
+    vector<Mode*> chain = { root.get(), mode.get() };
+    cout << mode->BuildHelpXML(chain);
+  }
+  cout << "</AllModes>" << endl;
+}
 
+int RunTool(int argc, char** argv, bool show_help) {
+  unique_ptr<Mode> root = RootMode(argv[0]);
   // Initialize arg parsing state.
   vector<Mode*> chain = { root.get() };
 
@@ -202,18 +218,23 @@ static bool ParseCommandLineFlags(int* argc, char*** argv) {
   // Inspired by https://github.com/gflags/gflags/issues/43#issuecomment-168280647.
   bool show_help = false;
   gflags::ParseCommandLineNonHelpFlags(argc, argv, true);
+
+  // Leverage existing helpxml flag to print mode/action xml.
+  if (FLAGS_helpxml) {
+    kudu::tools::DumpToolXML(*argv[0]);
+    exit(1);
+  }
+
   if (FLAGS_help ||
       FLAGS_helpshort ||
       !FLAGS_helpon.empty() ||
       !FLAGS_helpmatch.empty() ||
-      FLAGS_helppackage ||
-      FLAGS_helpxml) {
+      FLAGS_helppackage) {
     FLAGS_help = false;
     FLAGS_helpshort = false;
     FLAGS_helpon = "";
     FLAGS_helpmatch = "";
     FLAGS_helppackage = false;
-    FLAGS_helpxml = false;
     show_help = true;
   }
   kudu::HandleCommonFlags();