From 6abbb6677379719c965dafba960035a091261171 Mon Sep 17 00:00:00 2001
From: Ryan Curtin <ryan@ratml.org>
Date: Wed, 6 Mar 2019 19:56:30 -0500
Subject: [PATCH] Backport Markdown support onto mlpack 3.0.3.

---
 CMake/RunProgram.cmake                        |   8 +
 CMakeLists.txt                                |  14 +
 src/mlpack/CMakeLists.txt                     |   9 +-
 src/mlpack/bindings/CMakeLists.txt            |   2 +
 src/mlpack/bindings/cli/CMakeLists.txt        |   2 +
 .../bindings/cli/default_param_impl.hpp       |  59 +-
 src/mlpack/bindings/cli/end_program.hpp       |   2 +-
 .../bindings/cli/get_printable_type.hpp       |  79 +++
 .../bindings/cli/get_printable_type_impl.hpp  | 109 ++++
 .../bindings/cli/print_doc_functions.hpp      |  38 ++
 .../bindings/cli/print_doc_functions_impl.hpp | 110 +++-
 src/mlpack/bindings/cli/print_help.cpp        |   6 +-
 src/mlpack/bindings/cli/print_type_doc.hpp    |  81 +++
 .../bindings/cli/print_type_doc_impl.hpp      | 173 ++++++
 src/mlpack/bindings/cli/string_type_param.hpp |   6 -
 .../bindings/cli/string_type_param_impl.hpp   |  10 -
 src/mlpack/bindings/markdown/CMakeLists.txt   | 209 +++++++
 .../markdown/MarkdownCategories.cmake         |  11 +
 src/mlpack/bindings/markdown/binding_info.cpp |  49 ++
 src/mlpack/bindings/markdown/binding_info.hpp |  62 ++
 .../bindings/markdown/default_param.hpp       |  45 ++
 .../markdown/generate_markdown.binding.cpp.in |  46 ++
 .../markdown/generate_markdown.binding.hpp.in |  28 +
 .../markdown/generate_markdown.cpp.in         | 114 ++++
 .../bindings/markdown/get_binding_name.cpp    |  40 ++
 .../bindings/markdown/get_binding_name.hpp    |  30 +
 src/mlpack/bindings/markdown/get_param.hpp    |  38 ++
 .../bindings/markdown/get_printable_param.hpp | 126 ++++
 .../markdown/get_printable_param_name.hpp     |  83 +++
 .../get_printable_param_name_impl.hpp         |  79 +++
 .../markdown/get_printable_param_value.hpp    |  88 +++
 .../get_printable_param_value_impl.hpp        |  84 +++
 .../bindings/markdown/get_printable_type.hpp  |  62 ++
 .../bindings/markdown/is_serializable.hpp     |  63 ++
 src/mlpack/bindings/markdown/md_option.hpp    | 109 ++++
 .../bindings/markdown/print_doc_functions.hpp | 109 ++++
 .../markdown/print_doc_functions_impl.hpp     | 540 ++++++++++++++++++
 src/mlpack/bindings/markdown/print_docs.cpp   | 218 +++++++
 src/mlpack/bindings/markdown/print_docs.hpp   |  33 ++
 .../bindings/markdown/print_type_doc.hpp      |  46 ++
 .../bindings/markdown/program_doc_wrapper.hpp |  41 ++
 .../bindings/markdown/res/change_language.js  | 102 ++++
 .../bindings/markdown/res/formatting.css      | 142 +++++
 src/mlpack/bindings/markdown/res/menu_bg.png  | Bin 0 -> 395 bytes
 src/mlpack/bindings/python/CMakeLists.txt     |   4 +
 src/mlpack/bindings/python/default_param.hpp  |  95 +++
 .../bindings/python/default_param_impl.hpp    | 147 +++++
 .../bindings/python/get_cython_type.hpp       |  10 -
 .../bindings/python/get_printable_type.hpp    | 120 ++++
 .../python/get_printable_type_impl.hpp        | 151 +++++
 src/mlpack/bindings/python/print_doc.hpp      |  22 +-
 .../bindings/python/print_doc_functions.hpp   |  26 +
 .../python/print_doc_functions_impl.hpp       | 124 +++-
 .../python/print_input_processing.hpp         |   1 -
 src/mlpack/bindings/python/print_type_doc.hpp |  81 +++
 .../bindings/python/print_type_doc_impl.hpp   | 162 ++++++
 src/mlpack/bindings/python/py_option.hpp      |   4 +
 .../python/tests/test_python_binding_main.cpp |   1 +
 src/mlpack/core/util/cli.cpp                  |   3 +-
 src/mlpack/core/util/mlpack_main.hpp          |  68 ++-
 src/mlpack/core/util/param.hpp                |  31 +-
 src/mlpack/core/util/program_doc.cpp          |  30 +-
 src/mlpack/core/util/program_doc.hpp          |  17 +-
 src/mlpack/methods/CMakeLists.txt             |   4 +
 src/mlpack/methods/adaboost/CMakeLists.txt    |   1 +
 src/mlpack/methods/adaboost/adaboost_main.cpp |  21 +-
 src/mlpack/methods/approx_kfn/CMakeLists.txt  |   1 +
 .../methods/approx_kfn/approx_kfn_main.cpp    |  19 +-
 src/mlpack/methods/cf/CMakeLists.txt          |   1 +
 src/mlpack/methods/cf/cf_main.cpp             |  25 +-
 src/mlpack/methods/dbscan/CMakeLists.txt      |   1 +
 src/mlpack/methods/dbscan/dbscan_main.cpp     |  12 +-
 .../methods/decision_stump/CMakeLists.txt     |   1 +
 .../decision_stump/decision_stump_main.cpp    |  12 +-
 .../methods/decision_tree/CMakeLists.txt      |   1 +
 .../decision_tree/decision_tree_main.cpp      |  16 +-
 src/mlpack/methods/det/CMakeLists.txt         |   1 +
 src/mlpack/methods/det/det_main.cpp           |  15 +-
 src/mlpack/methods/emst/CMakeLists.txt        |   1 +
 src/mlpack/methods/emst/emst_main.cpp         |  13 +-
 src/mlpack/methods/fastmks/CMakeLists.txt     |   1 +
 src/mlpack/methods/fastmks/fastmks_main.cpp   |  15 +-
 src/mlpack/methods/gmm/CMakeLists.txt         |   5 +
 src/mlpack/methods/gmm/gmm_generate_main.cpp  |  12 +-
 .../methods/gmm/gmm_probability_main.cpp      |  13 +-
 src/mlpack/methods/gmm/gmm_train_main.cpp     |  13 +-
 src/mlpack/methods/hmm/CMakeLists.txt         |   7 +
 src/mlpack/methods/hmm/hmm_generate_main.cpp  |  18 +-
 src/mlpack/methods/hmm/hmm_loglik_main.cpp    |  19 +-
 src/mlpack/methods/hmm/hmm_train_main.cpp     |  21 +-
 src/mlpack/methods/hmm/hmm_viterbi_main.cpp   |  19 +-
 .../methods/hoeffding_trees/CMakeLists.txt    |   1 +
 .../hoeffding_trees/hoeffding_tree_main.cpp   |  14 +-
 src/mlpack/methods/kernel_pca/CMakeLists.txt  |   1 +
 .../methods/kernel_pca/kernel_pca_main.cpp    |  17 +-
 src/mlpack/methods/kmeans/CMakeLists.txt      |   1 +
 src/mlpack/methods/kmeans/kmeans_main.cpp     |  32 +-
 src/mlpack/methods/lars/CMakeLists.txt        |   1 +
 src/mlpack/methods/lars/lars_main.cpp         |  21 +-
 .../methods/linear_regression/CMakeLists.txt  |   1 +
 .../linear_regression_main.cpp                |  14 +-
 .../local_coordinate_coding/CMakeLists.txt    |   1 +
 .../local_coordinate_coding_main.cpp          |  14 +-
 .../logistic_regression/CMakeLists.txt        |   1 +
 .../logistic_regression_main.cpp              |  17 +-
 src/mlpack/methods/lsh/CMakeLists.txt         |   1 +
 src/mlpack/methods/lsh/lsh_main.cpp           |  17 +-
 src/mlpack/methods/mean_shift/CMakeLists.txt  |   1 +
 .../methods/mean_shift/mean_shift_main.cpp    |  23 +-
 src/mlpack/methods/naive_bayes/CMakeLists.txt |   1 +
 src/mlpack/methods/naive_bayes/nbc_main.cpp   |  14 +-
 src/mlpack/methods/nca/CMakeLists.txt         |   1 +
 src/mlpack/methods/nca/nca_main.cpp           |  15 +-
 .../methods/neighbor_search/CMakeLists.txt    |   3 +
 .../methods/neighbor_search/kfn_main.cpp      |  14 +-
 .../methods/neighbor_search/knn_main.cpp      |  17 +-
 src/mlpack/methods/nmf/CMakeLists.txt         |   1 +
 src/mlpack/methods/nmf/nmf_main.cpp           |  25 +-
 src/mlpack/methods/pca/CMakeLists.txt         |   1 +
 src/mlpack/methods/pca/pca_main.cpp           |  24 +-
 src/mlpack/methods/perceptron/CMakeLists.txt  |   1 +
 .../methods/perceptron/perceptron_main.cpp    |  13 +-
 src/mlpack/methods/preprocess/CMakeLists.txt  |   7 +
 .../preprocess/preprocess_binarize_main.cpp   |  13 +-
 .../preprocess/preprocess_describe_main.cpp   |  22 +-
 .../preprocess/preprocess_imputer_main.cpp    |  15 +-
 .../preprocess/preprocess_split_main.cpp      |  20 +-
 src/mlpack/methods/radical/CMakeLists.txt     |   1 +
 src/mlpack/methods/radical/radical_main.cpp   |  26 +-
 .../methods/random_forest/CMakeLists.txt      |   1 +
 .../random_forest/random_forest_main.cpp      |  17 +-
 .../methods/range_search/CMakeLists.txt       |   1 +
 .../range_search/range_search_main.cpp        |  16 +-
 src/mlpack/methods/rann/CMakeLists.txt        |   1 +
 src/mlpack/methods/rann/krann_main.cpp        |  17 +-
 .../methods/softmax_regression/CMakeLists.txt |   1 +
 .../softmax_regression_main.cpp               |  26 +-
 .../methods/sparse_coding/CMakeLists.txt      |   1 +
 .../sparse_coding/sparse_coding_main.cpp      |  33 +-
 139 files changed, 5011 insertions(+), 205 deletions(-)
 create mode 100644 CMake/RunProgram.cmake
 create mode 100644 src/mlpack/bindings/cli/get_printable_type.hpp
 create mode 100644 src/mlpack/bindings/cli/get_printable_type_impl.hpp
 create mode 100644 src/mlpack/bindings/cli/print_type_doc.hpp
 create mode 100644 src/mlpack/bindings/cli/print_type_doc_impl.hpp
 create mode 100644 src/mlpack/bindings/markdown/CMakeLists.txt
 create mode 100644 src/mlpack/bindings/markdown/MarkdownCategories.cmake
 create mode 100644 src/mlpack/bindings/markdown/binding_info.cpp
 create mode 100644 src/mlpack/bindings/markdown/binding_info.hpp
 create mode 100644 src/mlpack/bindings/markdown/default_param.hpp
 create mode 100644 src/mlpack/bindings/markdown/generate_markdown.binding.cpp.in
 create mode 100644 src/mlpack/bindings/markdown/generate_markdown.binding.hpp.in
 create mode 100644 src/mlpack/bindings/markdown/generate_markdown.cpp.in
 create mode 100644 src/mlpack/bindings/markdown/get_binding_name.cpp
 create mode 100644 src/mlpack/bindings/markdown/get_binding_name.hpp
 create mode 100644 src/mlpack/bindings/markdown/get_param.hpp
 create mode 100644 src/mlpack/bindings/markdown/get_printable_param.hpp
 create mode 100644 src/mlpack/bindings/markdown/get_printable_param_name.hpp
 create mode 100644 src/mlpack/bindings/markdown/get_printable_param_name_impl.hpp
 create mode 100644 src/mlpack/bindings/markdown/get_printable_param_value.hpp
 create mode 100644 src/mlpack/bindings/markdown/get_printable_param_value_impl.hpp
 create mode 100644 src/mlpack/bindings/markdown/get_printable_type.hpp
 create mode 100644 src/mlpack/bindings/markdown/is_serializable.hpp
 create mode 100644 src/mlpack/bindings/markdown/md_option.hpp
 create mode 100644 src/mlpack/bindings/markdown/print_doc_functions.hpp
 create mode 100644 src/mlpack/bindings/markdown/print_doc_functions_impl.hpp
 create mode 100644 src/mlpack/bindings/markdown/print_docs.cpp
 create mode 100644 src/mlpack/bindings/markdown/print_docs.hpp
 create mode 100644 src/mlpack/bindings/markdown/print_type_doc.hpp
 create mode 100644 src/mlpack/bindings/markdown/program_doc_wrapper.hpp
 create mode 100644 src/mlpack/bindings/markdown/res/change_language.js
 create mode 100644 src/mlpack/bindings/markdown/res/formatting.css
 create mode 100644 src/mlpack/bindings/markdown/res/menu_bg.png
 create mode 100644 src/mlpack/bindings/python/default_param.hpp
 create mode 100644 src/mlpack/bindings/python/default_param_impl.hpp
 create mode 100644 src/mlpack/bindings/python/get_printable_type.hpp
 create mode 100644 src/mlpack/bindings/python/get_printable_type_impl.hpp
 create mode 100644 src/mlpack/bindings/python/print_type_doc.hpp
 create mode 100644 src/mlpack/bindings/python/print_type_doc_impl.hpp

diff --git a/CMake/RunProgram.cmake b/CMake/RunProgram.cmake
new file mode 100644
index 000000000..6ae385314
--- /dev/null
+++ b/CMake/RunProgram.cmake
@@ -0,0 +1,8 @@
+# RunProgram.cmake: a CMake script that actually runs the given program to
+# generate a file, which is output into the given directory.
+#
+# This script depends on the following arguments:
+#
+#   PROGRAM: the program to run to.
+#   OUTPUT_FILE: the file to store the output in.
+execute_process(COMMAND ${PROGRAM} OUTPUT_FILE ${OUTPUT_FILE})
diff --git a/CMakeLists.txt b/CMakeLists.txt
index bf5c8a184..512415116 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -15,6 +15,20 @@ option(BUILD_CLI_EXECUTABLES "Build command-line executables." ON)
 option(BUILD_PYTHON_BINDINGS "Build Python bindings." ON)
 option(BUILD_SHARED_LIBS
     "Compile shared libraries (if OFF, static libraries are compiled)." ON)
+
+# Currently Python bindings aren't known to build successfully on Windows, so
+# set BUILD_PYTHON_BINDINGS to OFF when the platform is Windows.
+if (WIN32)
+  option(BUILD_PYTHON_BINDINGS "Build Python bindings." OFF)
+  message(WARNING "By default Python bindings are not compiled for Windows because they are not known to work.  Set BUILD_PYTHON_BINDINGS to ON if you want them built.")
+else ()
+  option(BUILD_PYTHON_BINDINGS "Build Python bindings." ON)
+endif()
+
+# Build Markdown bindings for documentation.  This is used as part of website
+# generation.
+option(BUILD_MARKDOWN_BINDINGS "Build Markdown bindings for website documentation." OFF)
+
 option(BUILD_WITH_COVERAGE
     "Build with support for code coverage tools (gcc only)." OFF)
 option(MATHJAX
diff --git a/src/mlpack/CMakeLists.txt b/src/mlpack/CMakeLists.txt
index 29c545c9e..b555e0100 100644
--- a/src/mlpack/CMakeLists.txt
+++ b/src/mlpack/CMakeLists.txt
@@ -14,7 +14,7 @@ set(DIRS
 )
 
 foreach(dir ${DIRS})
-    add_subdirectory(${dir})
+  add_subdirectory(${dir})
 endforeach()
 
 # MLPACK_SRCS is set in the subdirectories.  The dependencies (MLPACK_LIBRARIES)
@@ -40,8 +40,7 @@ if (NOT BUILD_SHARED_LIBS)
   add_definitions(-DMLPACK_STATIC_DEFINE)
 endif ()
 
-target_link_libraries(mlpack
-  ${MLPACK_LIBRARIES})
+target_link_libraries(mlpack ${MLPACK_LIBRARIES})
 
 set_target_properties(mlpack
   PROPERTIES
@@ -122,3 +121,7 @@ if (BUILD_PYTHON_BINDINGS)
   configure_file(${CMAKE_SOURCE_DIR}/src/mlpack/bindings/python/setup.py.in
                  ${CMAKE_BINARY_DIR}/src/mlpack/bindings/python/setup.py)
 endif ()
+
+# If we are building Markdown documentation, we have to run some setup after we
+# recurse into methods/.  If not, this function is empty.
+post_markdown_setup()
diff --git a/src/mlpack/bindings/CMakeLists.txt b/src/mlpack/bindings/CMakeLists.txt
index f35249ddd..e726c515b 100644
--- a/src/mlpack/bindings/CMakeLists.txt
+++ b/src/mlpack/bindings/CMakeLists.txt
@@ -1,6 +1,7 @@
 # All we have to do is recurse into the subdirectories.
 set(DIRS
   cli
+  markdown
   python
   tests
 )
@@ -9,6 +10,7 @@ foreach(dir ${DIRS})
   add_subdirectory(${dir})
 endforeach()
 
+set(MARKDOWN_CATEGORIES ${MARKDOWN_CATEGORIES} PARENT_SCOPE)
 set(MLPACK_SRCS ${MLPACK_SRCS} PARENT_SCOPE)
 set(MLPACK_PYXS ${MLPACK_PYXS} PARENT_SCOPE)
 set(DISABLE_CFLAGS ${DISABLE_CFLAGS} PARENT_SCOPE)
diff --git a/src/mlpack/bindings/cli/CMakeLists.txt b/src/mlpack/bindings/cli/CMakeLists.txt
index 83c50292e..280043fa6 100644
--- a/src/mlpack/bindings/cli/CMakeLists.txt
+++ b/src/mlpack/bindings/cli/CMakeLists.txt
@@ -25,6 +25,8 @@ set(SOURCES
   print_doc_functions_impl.hpp
   print_help.hpp
   print_help.cpp
+  print_type_doc.hpp
+  print_type_doc_impl.hpp
   set_param.hpp
   string_type_param.hpp
   string_type_param_impl.hpp
diff --git a/src/mlpack/bindings/cli/default_param_impl.hpp b/src/mlpack/bindings/cli/default_param_impl.hpp
index 8c1740c1f..8c9ea7896 100644
--- a/src/mlpack/bindings/cli/default_param_impl.hpp
+++ b/src/mlpack/bindings/cli/default_param_impl.hpp
@@ -32,7 +32,9 @@ std::string DefaultParamImpl(
         std::tuple<mlpack::data::DatasetInfo, arma::mat>>>::type* /* junk */)
 {
   std::ostringstream oss;
-  oss << boost::any_cast<T>(data.value);
+  if (!std::is_same<T, bool>::value)
+    oss << boost::any_cast<T>(data.value);
+
   return oss.str();
 }
 
@@ -48,9 +50,35 @@ std::string DefaultParamImpl(
   std::ostringstream oss;
   const T& vector = boost::any_cast<T>(data.value);
   oss << "[";
-  for (size_t i = 0; i < vector.size() - 1; ++i)
-    oss << vector[i] << " ";
-  oss << vector[vector.size() - 1] << "]";
+  if (std::is_same<T, std::vector<std::string>>::value)
+  {
+    if (vector.size() > 0)
+    {
+      for (size_t i = 0; i < vector.size() - 1; ++i)
+      {
+        oss << "'" << vector[i] << "', ";
+      }
+
+      oss << "'" << vector[vector.size() - 1] << "'";
+    }
+
+    oss << "]";
+  }
+  else
+  {
+    if (vector.size() > 0)
+    {
+      for (size_t i = 0; i < vector.size() - 1; ++i)
+      {
+        oss << vector[i] << ", ";
+      }
+
+      oss << vector[vector.size() - 1];
+    }
+
+    oss << "]";
+  }
+
   return oss.str();
 }
 
@@ -67,39 +95,30 @@ std::string DefaultParamImpl(
 }
 
 /**
- * Return the default value of a matrix option (this returns the default
- * filename, or '' if the default is no file).
+ * Return the default value of a matrix option (an empty filename).
  */
 template<typename T>
 std::string DefaultParamImpl(
-    const util::ParamData& data,
+    const util::ParamData& /* data */,
     const typename boost::enable_if_c<
         arma::is_arma_type<T>::value ||
         std::is_same<T, std::tuple<mlpack::data::DatasetInfo,
                                    arma::mat>>::value>::type* /* junk */)
 {
-  // Get the filename and return it, or return an empty string.
-  typedef std::tuple<T, std::string> TupleType;
-  const TupleType& tuple = *boost::any_cast<TupleType>(&data.value);
-  const std::string& filename = std::get<1>(tuple);
-  return "'" + filename + "'";
+  // The filename will always be empty.
+  return "''";
 }
 
 /**
- * Return the default value of a model option (this returns the default
- * filename, or '' if the default is no file).
+ * Return the default value of a model option (an empty filename).
  */
 template<typename T>
 std::string DefaultParamImpl(
-    const util::ParamData& data,
+    const util::ParamData& /* data */,
     const typename boost::disable_if<arma::is_arma_type<T>>::type* /* junk */,
     const typename boost::enable_if<data::HasSerialize<T>>::type* /* junk */)
 {
-  // Get the filename and return it, or return an empty string.
-  typedef std::tuple<T*, std::string> TupleType;
-  const TupleType& tuple = *boost::any_cast<TupleType>(&data.value);
-  const std::string& filename = std::get<1>(tuple);
-  return "'" + filename + "'";
+  return "''";
 }
 
 
diff --git a/src/mlpack/bindings/cli/end_program.hpp b/src/mlpack/bindings/cli/end_program.hpp
index 031c49d5f..8e412db10 100644
--- a/src/mlpack/bindings/cli/end_program.hpp
+++ b/src/mlpack/bindings/cli/end_program.hpp
@@ -50,7 +50,7 @@ inline void EndProgram()
     while (it != parameters.end())
     {
       // Now, figure out what type it is, and print it.
-      // We can handle strings, ints, bools, floats, doubles.
+      // We can handle strings, ints, bools, doubles.
       const util::ParamData& data = it->second;
       std::string boostName;
       CLI::GetSingleton().functionMap[data.tname]["MapParameterName"](data,
diff --git a/src/mlpack/bindings/cli/get_printable_type.hpp b/src/mlpack/bindings/cli/get_printable_type.hpp
new file mode 100644
index 000000000..c4634c4ad
--- /dev/null
+++ b/src/mlpack/bindings/cli/get_printable_type.hpp
@@ -0,0 +1,79 @@
+/**
+ * @file get_printable_type.hpp
+ * @author Ryan Curtin
+ *
+ * Get the printable type of a parameter.  This type is not the C++ type but
+ * instead the command-line type that a user would use.
+ */
+#ifndef MLPACK_BINDINGS_CLI_GET_PRINTABLE_TYPE_HPP
+#define MLPACK_BINDINGS_CLI_GET_PRINTABLE_TYPE_HPP
+
+namespace mlpack {
+namespace bindings {
+namespace cli {
+
+/**
+ * Return a string representing the command-line type of an option.
+ */
+template<typename T>
+std::string GetPrintableType(
+    const util::ParamData& data,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type* = 0,
+    const typename boost::disable_if<util::IsStdVector<T>>::type* = 0,
+    const typename boost::disable_if<data::HasSerialize<T>>::type* = 0,
+    const typename boost::disable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type* = 0);
+
+/**
+ * Return a string representing the command-line type of a vector.
+ */
+template<typename T>
+std::string GetPrintableType(
+    const util::ParamData& data,
+    const typename std::enable_if<util::IsStdVector<T>::value>::type* = 0);
+
+/**
+ * Return a string representing the command-line type of a matrix option.
+ */
+template<typename T>
+std::string GetPrintableType(
+    const util::ParamData& data,
+    const typename std::enable_if<arma::is_arma_type<T>::value>::type* = 0);
+
+/**
+ * Return a string representing the command-line type of a matrix tuple option.
+ */
+template<typename T>
+std::string GetPrintableType(
+    const util::ParamData& data,
+    const typename std::enable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>::value>::type* = 0);
+
+/**
+ * Return a string representing the command-line type of a model.
+ */
+template<typename T>
+std::string GetPrintableType(
+    const util::ParamData& data,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type* = 0,
+    const typename boost::enable_if<data::HasSerialize<T>>::type* = 0);
+
+/**
+ * Print the command-line type of an option into a string.
+ */
+template<typename T>
+void GetPrintableType(const util::ParamData& data,
+                       const void* /* input */,
+                       void* output)
+{
+  *((std::string*) output) =
+      GetPrintableType<typename std::remove_pointer<T>::type>(data);
+}
+
+} // namespace cli
+} // namespace bindings
+} // namespace mlpack
+
+#include "get_printable_type_impl.hpp"
+
+#endif
diff --git a/src/mlpack/bindings/cli/get_printable_type_impl.hpp b/src/mlpack/bindings/cli/get_printable_type_impl.hpp
new file mode 100644
index 000000000..bb1ff85a5
--- /dev/null
+++ b/src/mlpack/bindings/cli/get_printable_type_impl.hpp
@@ -0,0 +1,109 @@
+/**
+ * @file get_printable_type_impl.hpp
+ * @author Ryan Curtin
+ *
+ * Get the printable type of a parameter.  This type is not the C++ type but
+ * instead the command-line type that a user would use.
+ */
+#ifndef MLPACK_BINDINGS_CLI_GET_PRINTABLE_TYPE_IMPL_HPP
+#define MLPACK_BINDINGS_CLI_GET_PRINTABLE_TYPE_IMPL_HPP
+
+#include "get_printable_type.hpp"
+
+namespace mlpack {
+namespace bindings {
+namespace cli {
+
+/**
+ * Return a string representing the command-line type of an option.
+ */
+template<typename T>
+std::string GetPrintableType(
+    const util::ParamData& data,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type*,
+    const typename boost::disable_if<util::IsStdVector<T>>::type*,
+    const typename boost::disable_if<data::HasSerialize<T>>::type*,
+    const typename boost::disable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type*)
+{
+  if (std::is_same<T, bool>::value)
+    return "flag";
+  else if (std::is_same<T, int>::value)
+    return "int";
+  else if (std::is_same<T, double>::value)
+    return "double";
+  else if (std::is_same<T, std::string>::value)
+    return "string";
+  else
+    throw std::invalid_argument("unknown parameter type" + data.cppType);
+}
+
+/**
+ * Return a string representing the command-line type of a vector.
+ */
+template<typename T>
+std::string GetPrintableType(
+    const util::ParamData& data,
+    const typename std::enable_if<util::IsStdVector<T>::value>::type*)
+{
+  if (std::is_same<T, std::vector<int>>::value)
+    return "int vector";
+  else if (std::is_same<T, std::vector<std::string>>::value)
+    return "string vector";
+  else
+    throw std::invalid_argument("unknown vector type " + data.cppType);
+}
+
+/**
+ * Return a string representing the command-line type of a matrix option.
+ */
+template<typename T>
+std::string GetPrintableType(
+    const util::ParamData& data,
+    const typename std::enable_if<arma::is_arma_type<T>::value>::type*)
+{
+  if (std::is_same<T, arma::mat>::value)
+    return "2-d matrix file";
+  else if (std::is_same<T, arma::Mat<size_t>>::value)
+    return "2-d index matrix file";
+  else if (std::is_same<T, arma::rowvec>::value)
+    return "1-d matrix file";
+  else if (std::is_same<T, arma::Row<size_t>>::value)
+    return "1-d index matrix file";
+  else if (std::is_same<T, arma::vec>::value)
+    return "1-d matrix file";
+  else if (std::is_same<T, arma::Col<size_t>>::value)
+    return "1-d index matrix file";
+  else
+    throw std::invalid_argument("unknown Armadillo type" + data.cppType);
+}
+
+/**
+ * Return a string representing the command-line type of a matrix tuple option.
+ */
+template<typename T>
+std::string GetPrintableType(
+    const util::ParamData& /* data */,
+    const typename std::enable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>::value>::type*)
+{
+  return "2-d categorical matrix file";
+}
+
+/**
+ * Return a string representing the command-line type of a model.
+ */
+template<typename T>
+std::string GetPrintableType(
+    const util::ParamData& data,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type*,
+    const typename boost::enable_if<data::HasSerialize<T>>::type*)
+{
+  return data.cppType + " file";
+}
+
+} // namespace cli
+} // namespace bindings
+} // namespace mlpack
+
+#endif
diff --git a/src/mlpack/bindings/cli/print_doc_functions.hpp b/src/mlpack/bindings/cli/print_doc_functions.hpp
index cfc024b2c..81ce73816 100644
--- a/src/mlpack/bindings/cli/print_doc_functions.hpp
+++ b/src/mlpack/bindings/cli/print_doc_functions.hpp
@@ -20,12 +20,38 @@ namespace mlpack {
 namespace bindings {
 namespace cli {
 
+/**
+ * Given the name of a binding, print its command-line name (this returns
+ * "mlpack_<bindingName>".
+ */
+inline std::string GetBindingName(const std::string& bindingName);
+
+/**
+ * Print any imports for CLI (there are none, so this returns an empty string).
+ */
+inline std::string PrintImport(const std::string& bindingName);
+
+/**
+ * Print any special information about output options.
+ */
+inline std::string PrintOutputOptionInfo();
+
+/**
+ * Print documentation for each of the types.
+ */
+inline std::string PrintTypeDocs();
+
 /**
  * Given a parameter type, print the corresponding value.
  */
 template<typename T>
 inline std::string PrintValue(const T& value, bool quotes);
 
+/**
+ * Given a parameter name, print its corresponding default value.
+ */
+inline std::string PrintDefault(const std::string& paramName);
+
 /**
  * Print a dataset type parameter (add .csv and return).
  */
@@ -36,6 +62,12 @@ inline std::string PrintDataset(const std::string& dataset);
  */
 inline std::string PrintModel(const std::string& model);
 
+/**
+ * Print the type of a parameter that a user would specify from the
+ * command-line.
+ */
+inline std::string PrintType(const util::ParamData& param);
+
 /**
  * Base case for recursion.
  */
@@ -56,6 +88,12 @@ std::string ProcessOptions(const std::string& paramName,
 template<typename... Args>
 std::string ProgramCall(const std::string& programName, Args... args);
 
+/**
+ * Given a program name, print a program call invocation assuming that all
+ * options are specified.
+ */
+inline std::string ProgramCall(const std::string& programName);
+
 /**
  * Print what a user would type to invoke the given option name.  Note that the
  * name *must* exist in the CLI module.  (Note that because of the way
diff --git a/src/mlpack/bindings/cli/print_doc_functions_impl.hpp b/src/mlpack/bindings/cli/print_doc_functions_impl.hpp
index 0f37e7d5e..96349f5dd 100644
--- a/src/mlpack/bindings/cli/print_doc_functions_impl.hpp
+++ b/src/mlpack/bindings/cli/print_doc_functions_impl.hpp
@@ -20,6 +20,31 @@ namespace mlpack {
 namespace bindings {
 namespace cli {
 
+/**
+ * Given the name of a binding, print its command-line name (this returns
+ * "mlpack_<bindingName>".
+ */
+inline std::string GetBindingName(const std::string& bindingName)
+{
+  return "mlpack_" + bindingName;
+}
+
+/**
+ * Print any imports for CLI (there are none, so this returns an empty string).
+ */
+inline std::string PrintImport(const std::string& /* bindingName */)
+{
+  return "";
+}
+
+/**
+ * Print any special information about output options.
+ */
+inline std::string PrintOutputOptionInfo()
+{
+  return "";
+}
+
 /**
  * Given a parameter type, print the corresponding value.
  */
@@ -35,6 +60,23 @@ inline std::string PrintValue(const T& value, bool quotes)
   return oss.str();
 }
 
+/**
+ * Given a parameter name, print its corresponding default value.
+ */
+inline std::string PrintDefault(const std::string& paramName)
+{
+  if (CLI::Parameters().count(paramName) == 0)
+    throw std::invalid_argument("unknown parameter " + paramName + "!");
+
+  const util::ParamData& d = CLI::Parameters()[paramName];
+
+  std::string defaultValue;
+  CLI::GetSingleton().functionMap[d.tname]["DefaultParam"](d, NULL,
+      (void*) &defaultValue);
+
+  return defaultValue;
+}
+
 /**
  * Print a dataset type parameter (add .csv and return).
  */
@@ -107,10 +149,76 @@ std::string ProcessOptions(const std::string& paramName,
 template<typename... Args>
 std::string ProgramCall(const std::string& programName, Args... args)
 {
-  return util::HyphenateString("$ " + programName + " " +
+  return util::HyphenateString("$ " + GetBindingName(programName) + " " +
       ProcessOptions(args...), 2);
 }
 
+/**
+ * Given a program name, print a program call invocation assuming that all
+ * options are specified.
+ */
+inline std::string ProgramCall(const std::string& programName)
+{
+  std::ostringstream oss;
+  oss << "$ " << GetBindingName(programName);
+
+  // Handle all options---first input options, then output options.
+  const std::map<std::string, util::ParamData>& parameters = CLI::Parameters();
+
+  for (auto it = parameters.begin(); it != parameters.end(); ++it)
+  {
+    if (!it->second.input || it->second.persistent)
+      continue;
+
+    // Otherwise, print the name and the default value.
+    std::string name;
+    CLI::GetSingleton().functionMap[it->second.tname]["GetPrintableParamName"](
+        it->second, NULL, (void*) &name);
+
+    std::string value;
+    CLI::GetSingleton().functionMap[it->second.tname]["DefaultParam"](
+        it->second, NULL, (void*) &value);
+    if (value == "''")
+      value = "<string>";
+
+    oss << " ";
+    if (!it->second.required)
+      oss << "[";
+
+    oss << name;
+    if (it->second.cppType != "bool")
+      oss << " " << value;
+
+    if (!it->second.required)
+      oss << "]";
+  }
+
+  // Now get the output options.
+  for (auto it = parameters.begin(); it != parameters.end(); ++it)
+  {
+    if (it->second.input)
+      continue;
+
+    // Otherwise, print the name and the default value.
+    std::string name;
+    CLI::GetSingleton().functionMap[it->second.tname]["GetPrintableParamName"](
+        it->second, NULL, (void*) &name);
+
+    std::string value;
+    CLI::GetSingleton().functionMap[it->second.tname]["DefaultParam"](
+        it->second, NULL, (void*) &value);
+    if (value == "''")
+      value = "<string>";
+
+    oss << " [" << name;
+    if (it->second.cppType != "bool")
+      oss << " " << value;
+    oss << "]";
+  }
+
+  return util::HyphenateString(oss.str(), 8);
+}
+
 /**
  * Print what a user would type to invoke the given option name.  Note that the
  * name *must* exist in the CLI module.  (Note that because of the way
diff --git a/src/mlpack/bindings/cli/print_help.cpp b/src/mlpack/bindings/cli/print_help.cpp
index c6ef6c90d..ff62a7154 100644
--- a/src/mlpack/bindings/cli/print_help.cpp
+++ b/src/mlpack/bindings/cli/print_help.cpp
@@ -118,7 +118,11 @@ void PrintHelp(const std::string& param)
       }
 
       // Append default value to description.
-      if (pass >= 1 && data.cppType != "bool")
+      if (pass >= 1 && (data.cppType == "int" || data.cppType == "double" ||
+                        data.cppType == "std::string" ||
+                        data.cppType == "std::vector<int>" ||
+                        data.cppType == "std::vector<double>" ||
+                        data.cppType == "std::vector<std::string>"))
       {
         std::string defaultValue;
         CLI::GetSingleton().functionMap[data.tname]["DefaultParam"](data,
diff --git a/src/mlpack/bindings/cli/print_type_doc.hpp b/src/mlpack/bindings/cli/print_type_doc.hpp
new file mode 100644
index 000000000..677854e60
--- /dev/null
+++ b/src/mlpack/bindings/cli/print_type_doc.hpp
@@ -0,0 +1,81 @@
+/**
+ * @file print_type_doc.hpp
+ * @author Ryan Curtin
+ *
+ * Print documentation for a given type, detailing what the type actually is to
+ * the user.
+ */
+#ifndef MLPACK_BINDINGS_CLI_PRINT_TYPE_DOC_HPP
+#define MLPACK_BINDINGS_CLI_PRINT_TYPE_DOC_HPP
+
+#include <mlpack/core/util/is_std_vector.hpp>
+
+namespace mlpack {
+namespace bindings {
+namespace cli {
+
+/**
+ * Return a string representing the command-line type of an option.
+ */
+template<typename T>
+std::string PrintTypeDoc(
+    const util::ParamData& data,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type* = 0,
+    const typename boost::disable_if<util::IsStdVector<T>>::type* = 0,
+    const typename boost::disable_if<data::HasSerialize<T>>::type* = 0,
+    const typename boost::disable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type* = 0);
+
+/**
+ * Return a string representing the command-line type of a vector.
+ */
+template<typename T>
+std::string PrintTypeDoc(
+    const util::ParamData& data,
+    const typename std::enable_if<util::IsStdVector<T>::value>::type* = 0);
+
+/**
+ * Return a string representing the command-line type of a matrix option.
+ */
+template<typename T>
+std::string PrintTypeDoc(
+    const util::ParamData& data,
+    const typename std::enable_if<arma::is_arma_type<T>::value>::type* = 0);
+
+/**
+ * Return a string representing the command-line type of a matrix tuple option.
+ */
+template<typename T>
+std::string PrintTypeDoc(
+    const util::ParamData& data,
+    const typename std::enable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>::value>::type* = 0);
+
+/**
+ * Return a string representing the command-line type of a model.
+ */
+template<typename T>
+std::string PrintTypeDoc(
+    const util::ParamData& data,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type* = 0,
+    const typename boost::enable_if<data::HasSerialize<T>>::type* = 0);
+
+/**
+ * Print the command-line type of an option into a string.
+ */
+template<typename T>
+void PrintTypeDoc(const util::ParamData& data,
+                  const void* /* input */,
+                  void* output)
+{
+  *((std::string*) output) =
+      PrintTypeDoc<typename std::remove_pointer<T>::type>(data);
+}
+
+} // namespace cli
+} // namespace bindings
+} // namespace mlpack
+
+#include "print_type_doc_impl.hpp"
+
+#endif
diff --git a/src/mlpack/bindings/cli/print_type_doc_impl.hpp b/src/mlpack/bindings/cli/print_type_doc_impl.hpp
new file mode 100644
index 000000000..3a5a20912
--- /dev/null
+++ b/src/mlpack/bindings/cli/print_type_doc_impl.hpp
@@ -0,0 +1,173 @@
+/**
+ * @file print_type_doc_impl.hpp
+ * @author Ryan Curtin
+ *
+ * Print documentation for a given type.
+ */
+#ifndef MLPACK_BINDINGS_CLI_PRINT_TYPE_DOC_IMPL_HPP
+#define MLPACK_BINDINGS_CLI_PRINT_TYPE_DOC_IMPL_HPP
+
+#include "print_type_doc.hpp"
+
+namespace mlpack {
+namespace bindings {
+namespace cli {
+
+/**
+ * Return a string representing the command-line type of an option.
+ */
+template<typename T>
+std::string PrintTypeDoc(
+    const util::ParamData& data,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type*,
+    const typename boost::disable_if<util::IsStdVector<T>>::type*,
+    const typename boost::disable_if<data::HasSerialize<T>>::type*,
+    const typename boost::disable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type*)
+{
+  // A flag type.
+  if (std::is_same<T, bool>::value)
+  {
+    return "A boolean flag option.  If not specified, it is false; if "
+        "specified, it is true.";
+  }
+  // An integer.
+  else if (std::is_same<T, int>::value)
+  {
+    return "An integer (i.e., \"1\").";
+  }
+  // A floating point value.
+  else if (std::is_same<T, double>::value)
+  {
+    return "A floating-point number (i.e., \"0.5\").";
+  }
+  // A string.
+  else if (std::is_same<T, std::string>::value)
+  {
+    return "A character string (i.e., \"hello\").";
+  }
+  // Not sure what it is...
+  else
+  {
+    throw std::invalid_argument("unknown parameter type" + data.cppType);
+  }
+}
+
+/**
+ * Return a string representing the command-line type of a vector.
+ */
+template<typename T>
+std::string PrintTypeDoc(
+    const util::ParamData& data,
+    const typename std::enable_if<util::IsStdVector<T>::value>::type*)
+{
+  if (std::is_same<T, std::vector<int>>::value)
+  {
+    return "A vector of integers, separated by commas (i.e., \"1,2,3\").";
+  }
+  else if (std::is_same<T, std::vector<std::string>>::value)
+  {
+    return "A vector of strings, separated by commas (i.e., "
+        "\"hello\",\"goodbye\").";
+  }
+  else
+  {
+    throw std::invalid_argument("unknown vector type" + data.cppType);
+  }
+}
+
+/**
+ * Return a string representing the command-line type of a matrix option.
+ */
+template<typename T>
+std::string PrintTypeDoc(
+    const util::ParamData& data,
+    const typename std::enable_if<arma::is_arma_type<T>::value>::type*)
+{
+  if (std::is_same<T, arma::mat>::value)
+  {
+    return "A data matrix filename.  The file can be CSV (.csv), TSV (.csv), "
+        "ASCII (space-separated values, .txt), Armadillo ASCII (.txt), PGM "
+        "(.pgm), PPM (.ppm), Armadillo binary (.bin), or HDF5 (.h5, .hdf, "
+        ".hdf5, or .he5), if mlpack was compiled with HDF5 support.  The type "
+        "of the data is detected by the extension of the filename.  The storage"
+        " should be such that one row corresponds to one point, and one column "
+        "corresponds to one dimension (this is the typical storage format for "
+        "on-disk data).  All values of the matrix will be loaded as double-"
+        "precision floating point data.";
+  }
+  else if (std::is_same<T, arma::Mat<size_t>>::value)
+  {
+    return "A data matrix filename, where the matrix holds only non-negative "
+        "integer values.  This type is often used for labels or indices.  The "
+        "file can be CSV (.csv), TSV (.csv), ASCII (space-separated values, "
+        ".txt), Armadillo ASCII (.txt), PGM (.pgm), PPM (.ppm), Armadillo "
+        "binary (.bin), or HDF5 (.h5, .hdf, .hdf5, or .he5), if mlpack was "
+        "compiled with HDF5 support.  The type of the data is detected by the "
+        "extension of the filename.  The storage should be such that one row "
+        "corresponds to one point, and one column corresponds to one dimension "
+        "(this is the typical storage format for on-disk data).  All values of "
+        "the matrix will be loaded as unsigned integers.";
+  }
+  else if (std::is_same<T, arma::rowvec>::value ||
+           std::is_same<T, arma::vec>::value)
+  {
+    return "A one-dimensional vector filename.  This file can take the same "
+        "formats as the data matrix filenames; however, it must either contain "
+        "one row and many columns, or one column and many rows.";
+  }
+  else if (std::is_same<T, arma::Row<size_t>>::value ||
+           std::is_same<T, arma::Col<size_t>>::value)
+  {
+    return "A one-dimensional vector filename, where the matrix holds only non-"
+        "negative integer values.  This type is typically used for labels or "
+        "predictions or other indices.  This file can take the same formats as "
+        "the data matrix filenames; however, it must either contain one row and"
+        " many columns, or one column and many rows.";
+  }
+  else
+  {
+    throw std::invalid_argument("unknown matrix type " + data.cppType);
+  }
+}
+
+/**
+ * Return a string representing the command-line type of a matrix tuple option.
+ */
+template<typename T>
+std::string PrintTypeDoc(
+    const util::ParamData& /* data */,
+    const typename std::enable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>::value>::type*)
+{
+  return "A filename for a data matrix that can contain categorical "
+      "(non-numeric) data.  If the file contains only numeric data, then the "
+      "same formats for regular data matrices can be used.  If the file "
+      "contains strings or other values that can't be parsed as numbers, then "
+      "the type to be loaded must be CSV (.csv) or ARFF (.arff).  Any non-"
+      "numeric data will be converted to an unsigned integer value, and "
+      "dimensions where the data is converted will be treated as categorical "
+      "dimensions.  When using this format, there is no need for one-hot "
+      "encoding of categorical data.";
+}
+
+/**
+ * Return a string representing the command-line type of a model.
+ */
+template<typename T>
+std::string PrintTypeDoc(
+    const util::ParamData& /* data */,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type*,
+    const typename boost::enable_if<data::HasSerialize<T>>::type*)
+{
+  return "A filename containing an mlpack model.  These can have one of three "
+      "formats: binary (.bin), text (.txt), and XML (.xml).  The XML format "
+      "produces the largest (but most human-readable) files, while the binary "
+      "format can be significantly more compact and quicker to load and save.";
+}
+
+} // namespace cli
+} // namespace bindings
+} // namespace mlpack
+
+#endif
diff --git a/src/mlpack/bindings/cli/string_type_param.hpp b/src/mlpack/bindings/cli/string_type_param.hpp
index 28caa8991..702fb4668 100644
--- a/src/mlpack/bindings/cli/string_type_param.hpp
+++ b/src/mlpack/bindings/cli/string_type_param.hpp
@@ -74,12 +74,6 @@ inline void StringTypeParam<std::string>(const util::ParamData& /* data */,
                                          const void* /* input */,
                                          void* output);
 
-//! Return "float".
-template<>
-inline void StringTypeParam<float>(const util::ParamData& /* data */,
-                                   const void* /* input */,
-                                   void* output);
-
 //! Return "double".
 template<>
 inline void StringTypeParam<double>(const util::ParamData& /* data */,
diff --git a/src/mlpack/bindings/cli/string_type_param_impl.hpp b/src/mlpack/bindings/cli/string_type_param_impl.hpp
index 01065086e..86fb96302 100644
--- a/src/mlpack/bindings/cli/string_type_param_impl.hpp
+++ b/src/mlpack/bindings/cli/string_type_param_impl.hpp
@@ -80,16 +80,6 @@ inline void StringTypeParam<std::string>(const util::ParamData& /* data */,
   *outstr = "string";
 }
 
-//! Return "float".
-template<>
-inline void StringTypeParam<float>(const util::ParamData& /* data */,
-                                   const void* /* input */,
-                                   void* output)
-{
-  std::string* outstr = (std::string*) output;
-  *outstr = "float";
-}
-
 //! Return "double".
 template<>
 inline void StringTypeParam<double>(const util::ParamData& /* data */,
diff --git a/src/mlpack/bindings/markdown/CMakeLists.txt b/src/mlpack/bindings/markdown/CMakeLists.txt
new file mode 100644
index 000000000..b6b31cfba
--- /dev/null
+++ b/src/mlpack/bindings/markdown/CMakeLists.txt
@@ -0,0 +1,209 @@
+macro (not_found_return message)
+  message(STATUS "${message}")
+
+  macro (add_markdown_docs name languages category)
+    # Do nothing.
+  endmacro()
+
+  function (post_markdown_setup)
+    # Do nothing.
+  endfunction ()
+
+  return ()
+endmacro ()
+
+if (NOT BUILD_MARKDOWN_BINDINGS)
+  not_found_return("Not building Markdown bindings.")
+endif ()
+
+# We don't need to find any libraries or anything to generate markdown
+# documentation.
+
+# Get categories.  The list of allowable categories for add_markdown_docs() is
+# in that file.
+include(MarkdownCategories.cmake)
+set(MARKDOWN_CATEGORIES ${MARKDOWN_CATEGORIES} PARENT_SCOPE)
+
+# Add sources for Markdown bindings.
+set(SOURCES
+  "binding_info.hpp"
+  "binding_info.cpp"
+  "default_param.hpp"
+  "get_binding_name.hpp"
+  "get_binding_name.cpp"
+  "get_param.hpp"
+  "get_printable_param.hpp"
+  "get_printable_param_name.hpp"
+  "get_printable_param_name_impl.hpp"
+  "get_printable_type.hpp"
+  "md_option.hpp"
+  "print_doc_functions.hpp"
+  "print_doc_functions_impl.hpp"
+  "print_docs.hpp"
+  "print_docs.cpp"
+  "print_type_doc.hpp"
+  "program_doc_wrapper.hpp"
+)
+
+# Copy all Markdown sources to the build directory.
+add_custom_target(markdown_copy)
+add_custom_command(TARGET markdown_copy PRE_BUILD
+    COMMAND ${CMAKE_COMMAND} -E make_directory
+        ${CMAKE_BINARY_DIR}/src/mlpack/bindings/markdown/)
+foreach(file ${SOURCES})
+  add_custom_command(TARGET markdown_copy PRE_BUILD
+      COMMAND ${CMAKE_COMMAND} ARGS -E copy
+          ${CMAKE_CURRENT_SOURCE_DIR}/${file}
+          ${CMAKE_BINARY_DIR}/src/mlpack/bindings/markdown/)
+endforeach()
+
+# Create the add_markdown_docs() macro.  It's meant to be used as
+# 'add_markdown_docs(knn, "cli;python;julia", "classification")', for instance.
+# See the file 'MarkdownCategories.cmake' for valid categories that can be used
+# by the macro.
+macro (add_markdown_docs name languages category)
+
+  # First, make sure that the category is a valid category.
+  list(FIND MARKDOWN_CATEGORIES ${category} cat_index)
+  if (${cat_index} EQUAL -1)
+    string(CONCAT error_str "add_markdown_docs(): unknown category ${category}!"
+        "  See the categories in "
+        "src/mlpack/bindings/markdown/MarkdownCategories.cmake.")
+    message(FATAL_ERROR "${ERROR_STR}")
+  endif ()
+
+  # Next, we should use configure_file() to generate each
+  # generate_markdown.<binding>.cpp.  We need to loop over all the languages for
+  # this binding to do that.
+  set(BINDING ${name})
+  set(LANGUAGES_PUSH_BACK_CODE "")
+  set(PROGRAM_MAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/${name}_main.cpp")
+  foreach (lang ${languages})
+    set(LANGUAGES_PUSH_BACK_CODE
+        "${LANGUAGES_PUSH_BACK_CODE}\n  languages.push_back(\"${lang}\");")
+    set(MARKDOWN_ALL_LANGUAGES_LIST ${MARKDOWN_ALL_LANGUAGES_LIST} ${lang})
+  endforeach ()
+  list(REMOVE_DUPLICATES MARKDOWN_ALL_LANGUAGES_LIST)
+
+  # Do the actual file configuration.
+  set(BINDING_SOURCE_DIR ${CMAKE_SOURCE_DIR}/src/mlpack/bindings/markdown)
+  set(BINDING_BINARY_DIR ${CMAKE_BINARY_DIR}/src/mlpack/bindings/markdown)
+
+  configure_file(${BINDING_SOURCE_DIR}/generate_markdown.binding.hpp.in
+      ${BINDING_BINARY_DIR}/generate_markdown.${name}.hpp)
+  configure_file(${BINDING_SOURCE_DIR}/generate_markdown.binding.cpp.in
+      ${BINDING_BINARY_DIR}/generate_markdown.${name}.cpp)
+
+  # Lastly, that generate_markdown.<binding>.cpp should be added to the list of
+  # files to be compiled for the 'generate_markdown' target.  We also need to
+  # add information about this binding to a set of variables we have to track.
+  set (MARKDOWN_SRCS ${MARKDOWN_SRCS}
+      ${CMAKE_BINARY_DIR}/src/mlpack/bindings/markdown/generate_markdown.${name}.hpp
+      ${CMAKE_BINARY_DIR}/src/mlpack/bindings/markdown/generate_markdown.${name}.cpp)
+  set (MARKDOWN_SRCS ${MARKDOWN_SRCS} PARENT_SCOPE)
+  set (MARKDOWN_NAMES ${MARKDOWN_NAMES} ${name})
+  set (MARKDOWN_NAMES ${MARKDOWN_NAMES} PARENT_SCOPE)
+  set (MARKDOWN_NAME_CATEGORIES ${MARKDOWN_NAME_CATEGORIES} ${category})
+  set (MARKDOWN_NAME_CATEGORIES ${MARKDOWN_NAME_CATEGORIES} PARENT_SCOPE)
+  set (MARKDOWN_ALL_LANGUAGES_LIST ${MARKDOWN_ALL_LANGUAGES_LIST} PARENT_SCOPE)
+endmacro ()
+
+# After all the methods/ directories have been traversed, we can add the
+# 'generate_markdown' target.  This function is run at the bottom of
+# methods/CMakeLists.txt.
+function (post_markdown_setup)
+  # We need to generate the program file.  This consists of generating three
+  # things:
+  #
+  # - MARKDOWN_INCLUDES: the list of files to be included.
+  # - MARKDOWN_HEADER_CODE: the code used to print the header/sidebar.
+  # - MARKDOWN_CALL_CODE: the code to actually call the functions to print
+  #       documentation for each binding.
+
+  # Iterate over categories of binding.
+  list(LENGTH MARKDOWN_NAMES NUM_MARKDOWN_BINDINGS)
+  list(LENGTH MARKDOWN_CATEGORIES NUM_MARKDOWN_CATEGORIES)
+  math(EXPR cat_limit "${NUM_MARKDOWN_CATEGORIES} - 1")
+  foreach (i RANGE ${cat_limit})
+    list (GET MARKDOWN_CATEGORIES ${i} cat)
+    # Put the things in this category in a div.
+    string(CONCAT header_code "${MARKDOWN_HEADER_CODE}  cout << "
+        "\"<div class=\\\"category\\\" markdown=\\\"1\\\">\" << endl;\n  "
+        "cout << \"<h5>${cat}:</h5>\" << endl;\n")
+    set(MARKDOWN_HEADER_CODE ${header_code})
+
+    # Add an option for this binding.
+    math(EXPR range_limit "${NUM_MARKDOWN_BINDINGS} - 1")
+    foreach (j RANGE ${range_limit})
+      list (GET MARKDOWN_NAME_CATEGORIES ${j} category)
+      if (NOT category STREQUAL cat)
+        continue ()
+      endif ()
+
+      list (GET MARKDOWN_NAMES ${j} name)
+      set (MARKDOWN_HEADER_CODE
+          "${MARKDOWN_HEADER_CODE}\n  Print${name}Headers();")
+    endforeach ()
+
+    set (MARKDOWN_HEADER_CODE
+        "${MARKDOWN_HEADER_CODE}\n  cout << \"</div>\" << endl << endl;")
+  endforeach ()
+
+  foreach (name ${MARKDOWN_NAMES})
+    set (MARKDOWN_INCLUDE_CODE
+        "${MARKDOWN_INCLUDE_CODE}\n#include \"generate_markdown.${name}.hpp\"")
+    set (MARKDOWN_CALL_CODE "${MARKDOWN_CALL_CODE}\n  Print${name}Docs();")
+  endforeach ()
+
+  set(BINDING_SOURCE_DIR ${CMAKE_SOURCE_DIR}/src/mlpack/bindings/markdown)
+  set(BINDING_BINARY_DIR ${CMAKE_BINARY_DIR}/src/mlpack/bindings/markdown)
+
+  configure_file(${BINDING_SOURCE_DIR}/generate_markdown.cpp.in
+      ${BINDING_BINARY_DIR}/generate_markdown.cpp)
+
+  # Remember that this is being run from some other directory, so we have to be
+  # explicit with the locations of the files we are compiling against.
+  add_executable(generate_markdown
+      "${BINDING_BINARY_DIR}/generate_markdown.cpp"
+      ${MARKDOWN_SRCS}
+      "${BINDING_SOURCE_DIR}/binding_info.hpp"
+      "${BINDING_SOURCE_DIR}/binding_info.cpp"
+      "${BINDING_SOURCE_DIR}/default_param.hpp"
+      "${BINDING_SOURCE_DIR}/get_binding_name.hpp"
+      "${BINDING_SOURCE_DIR}/get_binding_name.cpp"
+      "${BINDING_SOURCE_DIR}/get_param.hpp"
+      "${BINDING_SOURCE_DIR}/get_printable_param.hpp"
+      "${BINDING_SOURCE_DIR}/get_printable_param_name.hpp"
+      "${BINDING_SOURCE_DIR}/get_printable_param_name_impl.hpp"
+      "${BINDING_SOURCE_DIR}/md_option.hpp"
+      "${BINDING_SOURCE_DIR}/print_doc_functions.hpp"
+      "${BINDING_SOURCE_DIR}/print_doc_functions_impl.hpp"
+      "${BINDING_SOURCE_DIR}/print_docs.hpp"
+      "${BINDING_SOURCE_DIR}/print_docs.cpp"
+      "${BINDING_SOURCE_DIR}/program_doc_wrapper.hpp"
+      "${BINDING_SOURCE_DIR}/generate_markdown.cpp")
+  target_link_libraries(generate_markdown mlpack ${MLPACK_LIBRARIES})
+  add_dependencies(generate_markdown markdown_copy)
+  set_target_properties(generate_markdown PROPERTIES
+      COMPILE_FLAGS -DBINDING_TYPE=BINDING_TYPE_MARKDOWN
+      RUNTIME_OUTPUT_DIRECTORY ${BINDING_BINARY_DIR})
+
+  add_custom_target(markdown ALL
+      ${CMAKE_COMMAND} -E make_directory
+          ${CMAKE_BINARY_DIR}/doc/
+      COMMAND ${CMAKE_COMMAND}
+          -DPROGRAM=${BINDING_BINARY_DIR}/generate_markdown
+          -DOUTPUT_FILE=${CMAKE_BINARY_DIR}/doc/mlpack.md
+          -P ${CMAKE_SOURCE_DIR}/CMake/RunProgram.cmake
+      COMMAND ${CMAKE_COMMAND} -E copy
+          ${BINDING_SOURCE_DIR}/res/change_language.js
+          ${CMAKE_BINARY_DIR}/doc/res/change_languages.js
+      COMMAND ${CMAKE_COMMAND} -E copy
+          ${BINDING_SOURCE_DIR}/res/menu_bg.png
+          ${CMAKE_BINARY_DIR}/doc/res/menu_bg.png
+      COMMAND ${CMAKE_COMMAND} -E copy
+          ${BINDING_SOURCE_DIR}/res/formatting.css
+          ${CMAKE_BINARY_DIR}/doc/res/formatting.css
+      DEPENDS generate_markdown
+      COMMENT "Generating Markdown documentation for mlpack bindings...")
+endfunction ()
diff --git a/src/mlpack/bindings/markdown/MarkdownCategories.cmake b/src/mlpack/bindings/markdown/MarkdownCategories.cmake
new file mode 100644
index 000000000..6b8d697cf
--- /dev/null
+++ b/src/mlpack/bindings/markdown/MarkdownCategories.cmake
@@ -0,0 +1,11 @@
+# This is a list of categories of binding that Markdown can handle.  If the
+# category you choose for a Markdown binding is not in this list, an error will
+# be thrown.
+set (MARKDOWN_CATEGORIES
+    "classification"
+    "regression"
+    "clustering"
+    "geometry"
+    "preprocessing"
+    "misc. / other"
+    "transformations")
diff --git a/src/mlpack/bindings/markdown/binding_info.cpp b/src/mlpack/bindings/markdown/binding_info.cpp
new file mode 100644
index 000000000..0a61103d3
--- /dev/null
+++ b/src/mlpack/bindings/markdown/binding_info.cpp
@@ -0,0 +1,49 @@
+/**
+ * @file binding_info.cpp
+ * @author Ryan Curtin
+ *
+ * Implementation of BindingInfo functions.
+ */
+#include "binding_info.hpp"
+
+using namespace std;
+
+namespace mlpack {
+namespace bindings {
+namespace markdown {
+
+util::ProgramDoc& BindingInfo::GetProgramDoc(const std::string& bindingName)
+{
+  if (GetSingleton().map.count(bindingName) == 0)
+  {
+    throw std::invalid_argument("Binding name '" + bindingName +
+        "' not known!");
+  }
+
+  return GetSingleton().map.at(bindingName);
+}
+
+/**
+ * Register a ProgramDoc object with the given bindingName.
+ */
+void BindingInfo::RegisterProgramDoc(const std::string& bindingName,
+                                     const util::ProgramDoc& programDoc)
+{
+  GetSingleton().map[bindingName] = programDoc;
+}
+
+//! Get or modify the current language (don't set it to something invalid!).
+std::string& BindingInfo::Language()
+{
+  return GetSingleton().language;
+}
+
+BindingInfo& BindingInfo::GetSingleton()
+{
+  static BindingInfo instance;
+  return instance;
+}
+
+} // namespace markdown
+} // namespace bindings
+} // namespace mlpack
diff --git a/src/mlpack/bindings/markdown/binding_info.hpp b/src/mlpack/bindings/markdown/binding_info.hpp
new file mode 100644
index 000000000..6a99d2ebc
--- /dev/null
+++ b/src/mlpack/bindings/markdown/binding_info.hpp
@@ -0,0 +1,62 @@
+/**
+ * @file binding_name.hpp
+ * @author Ryan Curtin
+ *
+ * This file defines the BindingInfo singleton class that is used specifically
+ * for the Markdown bindings to map from a binding name (i.e. "knn") to
+ * multiple ProgramDoc objects, which are then used to generate the
+ * documentation.
+ */
+#ifndef MLPACK_BINDINGS_MARKDOWN_BINDING_NAME_HPP
+#define MLPACK_BINDINGS_MARKDOWN_BINDING_NAME_HPP
+
+#include <mlpack/prereqs.hpp>
+#include <mlpack/core/util/program_doc.hpp>
+
+namespace mlpack {
+namespace bindings {
+namespace markdown {
+
+/**
+ * The BindingInfo class is used by the Markdown documentation generator to
+ * store multiple ProgramDoc objects, indexed by both the binding name (i.e.
+ * "knn") and the language (i.e. "cli").
+ */
+class BindingInfo
+{
+ public:
+  /**
+   * Return a ProgramDoc object for a given bindingName.
+   */
+  static util::ProgramDoc& GetProgramDoc(const std::string& bindingName);
+
+  /**
+   * Register a ProgramDoc object with the given bindingName.
+   */
+  static void RegisterProgramDoc(const std::string& bindingName,
+                                 const util::ProgramDoc& programDoc);
+
+  //! Get or modify the current language (don't set it to something invalid!).
+  static std::string& Language();
+
+ private:
+  //! Private constructor, so that only one instance can be created.
+  BindingInfo() { }
+
+  //! Get the singleton.
+  static BindingInfo& GetSingleton();
+
+  //! Internally-held map for mapping a binding name to a ProgramDoc name.
+  std::unordered_map<std::string, util::ProgramDoc> map;
+
+  //! Holds the name of the language that we are currently printing.  This is
+  //! modified before printing the documentation, and then used by
+  //! print_doc_functions.hpp during printing to print the correct language.
+  std::string language;
+};
+
+} // namespace markdown
+} // namespace bindings
+} // namespace mlpack
+
+#endif
diff --git a/src/mlpack/bindings/markdown/default_param.hpp b/src/mlpack/bindings/markdown/default_param.hpp
new file mode 100644
index 000000000..78af807e0
--- /dev/null
+++ b/src/mlpack/bindings/markdown/default_param.hpp
@@ -0,0 +1,45 @@
+/**
+ * @file default_param.hpp
+ * @author Ryan Curtin
+ *
+ * Get the default value of the parameter.  This depends on
+ * BindingInfo::Language() to choose which language to return the type for.
+ */
+#ifndef MLPACK_BINDINGS_MARKDOWN_DEFAULT_PARAM_HPP
+#define MLPACK_BINDINGS_MARKDOWN_DEFAULT_PARAM_HPP
+
+#include "binding_info.hpp"
+
+#include <mlpack/bindings/cli/default_param.hpp>
+#include <mlpack/bindings/python/default_param.hpp>
+
+namespace mlpack {
+namespace bindings {
+namespace markdown {
+
+/**
+ * Print the default value of a parameter into the output string.  The type
+ * printed depends on the current setting of BindingInfo::Language().
+ */
+template<typename T>
+void DefaultParam(const util::ParamData& data,
+                  const void* /* input */,
+                  void* output)
+{
+  if (BindingInfo::Language() == "cli")
+  {
+    *((std::string*) output) =
+        cli::DefaultParamImpl<typename std::remove_pointer<T>::type>(data);
+  }
+  else if (BindingInfo::Language() == "python")
+  {
+    *((std::string*) output) =
+        python::DefaultParamImpl<typename std::remove_pointer<T>::type>(data);
+  }
+}
+
+} // namespace markdown
+} // namespace bindings
+} // namespace mlpack
+
+#endif
diff --git a/src/mlpack/bindings/markdown/generate_markdown.binding.cpp.in b/src/mlpack/bindings/markdown/generate_markdown.binding.cpp.in
new file mode 100644
index 000000000..18a07e5ec
--- /dev/null
+++ b/src/mlpack/bindings/markdown/generate_markdown.binding.cpp.in
@@ -0,0 +1,46 @@
+/**
+ * @file generate_markdown.binding.cpp.in
+ * @author Ryan Curtin
+ *
+ * Print Markdown for a specific binding.  This provides two utility
+ * methods---one that prints info for a table of contents, and one that prints
+ * the Markdown bindings themselves.
+ */
+#define BINDING_NAME "${BINDING}"
+
+#include <mlpack/core.hpp>
+#include "generate_markdown.${BINDING}.hpp"
+#include "binding_info.hpp"
+#include "print_docs.hpp"
+#include "get_binding_name.hpp"
+
+static const std::string testName = "${BINDING}";
+#include <${PROGRAM_MAIN_FILE}>
+
+using namespace std;
+
+namespace mlpack {
+namespace bindings {
+namespace markdown {
+
+void Print${BINDING}Headers()
+{
+  // Fill the vector of languages for which we want to print.
+  vector<string> languages;
+  ${LANGUAGES_PUSH_BACK_CODE}
+
+  PrintHeaders("${BINDING}", languages);
+}
+
+void Print${BINDING}Docs()
+{
+  // Fill the vector of languages for which we want to print.
+  vector<string> languages;
+  ${LANGUAGES_PUSH_BACK_CODE}
+
+  PrintDocs("${BINDING}", languages);
+}
+
+} // namespace markdown
+} // namespace bindings
+} // namespace mlpack
diff --git a/src/mlpack/bindings/markdown/generate_markdown.binding.hpp.in b/src/mlpack/bindings/markdown/generate_markdown.binding.hpp.in
new file mode 100644
index 000000000..3e81c9c3a
--- /dev/null
+++ b/src/mlpack/bindings/markdown/generate_markdown.binding.hpp.in
@@ -0,0 +1,28 @@
+/**
+ * @file generate_markdown.binding.hpp.in
+ * @author Ryan Curtin
+ *
+ * Print Markdown for a specific binding.  This provides two utility
+ * methods---one that prints info for a table of contents, and one that prints
+ * the Markdown bindings themselves.
+ */
+#ifndef MLPACK_BINDINGS_MARKDOWN_GENERATE_MARKDOWN_${BINDING}_HPP
+#define MLPACK_BINDINGS_MARKDOWN_GENERATE_MARKDOWN_${BINDING}_HPP
+
+#include "print_docs.hpp"
+#include "get_binding_name.hpp"
+
+using namespace std;
+
+namespace mlpack {
+namespace bindings {
+namespace markdown {
+
+void Print${BINDING}Headers();
+void Print${BINDING}Docs();
+
+} // namespace markdown
+} // namespace bindings
+} // namespace mlpack
+
+#endif
diff --git a/src/mlpack/bindings/markdown/generate_markdown.cpp.in b/src/mlpack/bindings/markdown/generate_markdown.cpp.in
new file mode 100644
index 000000000..abacae377
--- /dev/null
+++ b/src/mlpack/bindings/markdown/generate_markdown.cpp.in
@@ -0,0 +1,114 @@
+/**
+ * @file generate_markdown.cpp.in
+ * @author Ryan Curtin
+ *
+ * This file is configured by CMake to generate all of the Markdown required by
+ * the project.
+ */
+#include <mlpack/core.hpp>
+#include "binding_info.hpp"
+#include "print_doc_functions.hpp"
+${MARKDOWN_INCLUDE_CODE}
+
+using namespace mlpack;
+using namespace mlpack::bindings;
+using namespace mlpack::bindings::markdown;
+using namespace std;
+
+int main()
+{
+  // These come to use from CMake separated by semicolons.
+  string languageList = "${MARKDOWN_ALL_LANGUAGES_LIST}";
+  vector<string> languages;
+  size_t index;
+  while ((index = languageList.find(';')) != string::npos)
+  {
+    languages.push_back(languageList.substr(0, index));
+    languageList = languageList.substr(index + 1);
+  }
+  languages.push_back(languageList);
+
+  cout << "<div id=\"header\" markdown=\"1\">" << endl;
+
+  // We need to create the input form selector for the language.
+  cout << "<form>" << endl;
+  cout << "  <div class=\"language-select-div\">" << endl;
+  cout << "  <select id=\"language-select\" onchange=\"changeLanguage()\">"
+      << endl;
+
+  // Print the first manually, because it is selected.
+  cout << "    <option value=\"" << languages[0] << "\" selected>"
+      << languages[0] << "</option>" << endl;
+  for (size_t i = 1; i < languages.size(); ++i)
+  {
+    cout << "    <option value=\"" << languages[i] << "\">" << languages[i]
+        << "</option>" << endl;
+  }
+
+  cout << "  </select>" << endl;
+  cout << "  </div>" << endl;
+  cout << "</form>" << endl;
+  cout << endl;
+
+  // The links to the data type sections get put here.
+  cout << " - [mlpack overview](#mlpack-overview){: .language-link #always }"
+      << endl;
+  for (size_t i = 0; i < languages.size(); ++i)
+  {
+    cout << " - [data formats](#" << languages[i] << "_data-formats){: "
+        << ".language-link #" << languages[i] << " }" << endl;
+  }
+
+  ${MARKDOWN_HEADER_CODE}
+
+  cout << endl << "</div>" << endl << endl;
+
+  cout << "<div id=\"docs\" markdown=\"1\">" << endl;
+  cout << endl;
+
+  // Create all the headers for each language.
+  for (size_t i = 0; i < languages.size(); ++i)
+  {
+    cout << "<div id=\"" << languages[i] << "\" class=\"language-header\" "
+        << "markdown=\"1\">" << endl;
+    cout << "# " << util::GetVersion() << " " << PrintLanguage(languages[i])
+        << " binding documentation" << endl;
+    cout << "</div>" << endl;
+  }
+
+  /**
+   * "mlpack overview" section.  This will go at the top of the page.
+   */
+  cout << "## mlpack overview" << endl;
+  cout << endl;
+  cout << "mlpack is an intuitive, fast, and flexible C++ machine learning "
+      "library with bindings to other languages.  It is meant to be a machine "
+      "learning analog to LAPACK, and aims to implement a wide array of machine"
+      " learning methods and functions as a \"swiss army knife\" for machine "
+      "learning researchers.";
+  cout << endl << endl;
+  cout << "This reference page details mlpack's bindings to other languages. "
+      "Further useful mlpack documentation links are given below.";
+  cout << endl << endl;
+  cout << " - [mlpack homepage](https://www.mlpack.org/)" << endl;
+  cout << " - [mlpack on Github](https://github.com/mlpack/mlpack)" << endl;
+  cout << " - [mlpack main documentation page]"
+      << "(https://www.mlpack.org/docs.html)" << endl;
+  cout << endl;
+
+  /**
+   * Discussion of different data types section.  This goes just below the
+   * overview section at the top of the page.
+   */
+  for (size_t i = 0; i < languages.size(); ++i)
+  {
+    BindingInfo::Language() = languages[i];
+    cout << PrintTypeDocs() << endl;
+  }
+
+  ${MARKDOWN_CALL_CODE}
+  cout << "</div>" << endl << endl;
+
+  // Make sure script gets included for changeLanguage().
+  cout << "<script src=\"res/change_language.js\"></script>" << endl;
+}
diff --git a/src/mlpack/bindings/markdown/get_binding_name.cpp b/src/mlpack/bindings/markdown/get_binding_name.cpp
new file mode 100644
index 000000000..67d229b6c
--- /dev/null
+++ b/src/mlpack/bindings/markdown/get_binding_name.cpp
@@ -0,0 +1,40 @@
+/**
+ * @file get_binding_name.cpp
+ * @author Ryan Curtin
+ *
+ * Given the name of a binding as it appears in CMake, return the corresponding
+ * name of the binding that is generated for a given language.
+ */
+#include "get_binding_name.hpp"
+
+namespace mlpack {
+namespace bindings {
+namespace markdown {
+
+std::string GetBindingName(const std::string& language,
+                           const std::string& name)
+{
+  // Unfortunately, every time a new binding is added, this code will need to be
+  // modified.
+  if (language == "cli")
+  {
+    // For command-line programs, all bindings have 'mlpack_' prepended to the
+    // name.
+    return "mlpack_" + name;
+  }
+  else if (language == "python")
+  {
+    // For Python bindings, the name is unchanged.
+    return name;
+  }
+  else
+  {
+    throw std::invalid_argument("Don't know how to compute binding name for "
+        "language \"" + language + "\"!  Is the language specified in "
+        "src/mlpack/bindings/markdown/get_binding_name.cpp?");
+  }
+}
+
+} // namespace markdown
+} // namespace bindings
+} // namespace mlpack
diff --git a/src/mlpack/bindings/markdown/get_binding_name.hpp b/src/mlpack/bindings/markdown/get_binding_name.hpp
new file mode 100644
index 000000000..21c55f05c
--- /dev/null
+++ b/src/mlpack/bindings/markdown/get_binding_name.hpp
@@ -0,0 +1,30 @@
+/**
+ * @file get_binding_name.cpp
+ * @author Ryan Curtin
+ *
+ * Given the name of a binding as it appears in CMake, return the corresponding
+ * name of the binding that is generated for a given language.
+ */
+#ifndef MLPACK_BINDINGS_MARKDOWN_GET_BINDING_NAME_HPP
+#define MLPACK_BINDINGS_MARKDOWN_GET_BINDING_NAME_HPP
+
+#include <mlpack/prereqs.hpp>
+
+namespace mlpack {
+namespace bindings {
+namespace markdown {
+
+/**
+ * Given a language name and a binding name, return the name of that binding for
+ * that language.  Note that if a new language is added to the mlpack bindings,
+ * this method will need to be updated so that documentation can be successfully
+ * generated for that language.
+ */
+std::string GetBindingName(const std::string& language,
+                           const std::string& name);
+
+} // namespace markdown
+} // namespace bindings
+} // namespace mlpack
+
+#endif
diff --git a/src/mlpack/bindings/markdown/get_param.hpp b/src/mlpack/bindings/markdown/get_param.hpp
new file mode 100644
index 000000000..6c1611f49
--- /dev/null
+++ b/src/mlpack/bindings/markdown/get_param.hpp
@@ -0,0 +1,38 @@
+/**
+ * @file get_param.hpp
+ * @author Ryan Curtin
+ *
+ * Get a parameter for a Markdown binding.
+ *
+ * mlpack is free software; you may redistribute it and/or modify it under the
+ * terms of the 3-clause BSD license.  You should have received a copy of the
+ * 3-clause BSD license along with mlpack.  If not, see
+ * http://www.opensource.org/licenses/BSD-3-Clause for more information.
+ */
+#ifndef MLPACK_BINDINGS_MARKDOWN_GET_PARAM_HPP
+#define MLPACK_BINDINGS_MARKDOWN_GET_PARAM_HPP
+
+#include <mlpack/prereqs.hpp>
+
+namespace mlpack {
+namespace bindings {
+namespace markdown {
+
+/**
+ * All Markdown binding types are exactly what is held in the ParamData, so no
+ * special handling is necessary.
+ */
+template<typename T>
+void GetParam(const util::ParamData& d,
+              const void* /* input */,
+              void* output)
+{
+  util::ParamData& dmod = const_cast<util::ParamData&>(d);
+  *((T**) output) = boost::any_cast<T>(&dmod.value);
+}
+
+} // namespace markdown
+} // namespace bindings
+} // namespace mlpack
+
+#endif
diff --git a/src/mlpack/bindings/markdown/get_printable_param.hpp b/src/mlpack/bindings/markdown/get_printable_param.hpp
new file mode 100644
index 000000000..53a862b61
--- /dev/null
+++ b/src/mlpack/bindings/markdown/get_printable_param.hpp
@@ -0,0 +1,126 @@
+/**
+ * @file get_printable_param.hpp
+ * @author Ryan Curtin
+ *
+ * Get a printable version of parameters.
+ *
+ * mlpack is free software; you may redistribute it and/or modify it under the
+ * terms of the 3-clause BSD license.  You should have received a copy of the
+ * 3-clause BSD license along with mlpack.  If not, see
+ * http://www.opensource.org/licenses/BSD-3-Clause for more information.
+ */
+#ifndef MLPACK_BINDINGS_MARKDOWN_GET_PRINTABLE_PARAM_HPP
+#define MLPACK_BINDINGS_MARKDOWN_GET_PRINTABLE_PARAM_HPP
+
+#include <mlpack/prereqs.hpp>
+#include <mlpack/core/util/is_std_vector.hpp>
+
+namespace mlpack {
+namespace bindings {
+namespace markdown {
+
+/**
+ * Print an option of a simple type.
+ */
+template<typename T>
+std::string GetPrintableParam(
+    const util::ParamData& data,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type* = 0,
+    const typename boost::disable_if<util::IsStdVector<T>>::type* = 0,
+    const typename boost::disable_if<data::HasSerialize<T>>::type* = 0,
+    const typename boost::disable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type* = 0)
+{
+  std::ostringstream oss;
+  oss << boost::any_cast<T>(data.value);
+  return oss.str();
+}
+
+/**
+ * Print a vector option, with spaces between it.
+ */
+template<typename T>
+std::string GetPrintableParam(
+    const util::ParamData& data,
+    const typename boost::enable_if<util::IsStdVector<T>>::type* = 0)
+{
+  const T& t = boost::any_cast<T>(data.value);
+
+  std::ostringstream oss;
+  for (size_t i = 0; i < t.size(); ++i)
+    oss << t[i] << " ";
+  return oss.str();
+}
+
+/**
+ * Print a matrix option (this prints its size).
+ */
+template<typename T>
+std::string GetPrintableParam(
+    const util::ParamData& data,
+    const typename boost::enable_if<arma::is_arma_type<T>>::type* = 0)
+{
+  // Get the matrix.
+  const T& matrix = boost::any_cast<T>(data.value);
+
+  std::ostringstream oss;
+  oss << matrix.n_rows << "x" << matrix.n_cols << " matrix";
+  return oss.str();
+}
+
+/**
+ * Print a serializable class option (this prints the class name).
+ */
+template<typename T>
+std::string GetPrintableParam(
+    const util::ParamData& data,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type* = 0,
+    const typename boost::enable_if<data::HasSerialize<T>>::type* = 0)
+{
+  std::ostringstream oss;
+  oss << data.cppType << " model at " << boost::any_cast<T*>(data.value);
+  return oss.str();
+}
+
+/**
+ * Print a combination DatasetInfo/matrix parameter.
+ */
+template<typename T>
+std::string GetPrintableParam(
+    const util::ParamData& data,
+    const typename boost::enable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type* = 0)
+{
+  // Get the matrix.
+  const T& tuple = boost::any_cast<T>(data.value);
+  const arma::mat& matrix = std::get<1>(tuple);
+
+  std::ostringstream oss;
+  oss << matrix.n_rows << "x" << matrix.n_cols << " matrix with dimension type "
+      << "information";
+  return oss.str();
+}
+
+/**
+ * Print an option into a std::string.  This should print a short, one-line
+ * representation of the object.  The string will be stored in the output
+ * pointer.
+ *
+ * @param data Parameter data struct.
+ * @param input Unused parameter.
+ * @param output Output storage for the string.
+ */
+template<typename T>
+void GetPrintableParam(const util::ParamData& data,
+                       const void* /* input */,
+                       void* output)
+{
+  *((std::string*) output) =
+      GetPrintableParam<typename std::remove_pointer<T>::type>(data);
+}
+
+} // namespace markdown
+} // namespace bindings
+} // namespace mlpack
+
+#endif
diff --git a/src/mlpack/bindings/markdown/get_printable_param_name.hpp b/src/mlpack/bindings/markdown/get_printable_param_name.hpp
new file mode 100644
index 000000000..9ceca6b4c
--- /dev/null
+++ b/src/mlpack/bindings/markdown/get_printable_param_name.hpp
@@ -0,0 +1,83 @@
+/**
+ * @file get_printable_param_name.hpp
+ * @author Ryan Curtin
+ *
+ * Return the parameter name that the user would specify on the command line,
+ * with different behavior for different parameter types.
+ *
+ * mlpack is free software; you may redistribute it and/or modify it under the
+ * terms of the 3-clause BSD license.  You should have received a copy of the
+ * 3-clause BSD license along with mlpack.  If not, see
+ * http://www.opensource.org/licenses/BSD-3-Clause for more information.
+ */
+#ifndef MLPACK_BINDINGS_MARKDOWN_GET_PRINTABLE_PARAM_NAME_HPP
+#define MLPACK_BINDINGS_MARKDOWN_GET_PRINTABLE_PARAM_NAME_HPP
+
+#include <mlpack/prereqs.hpp>
+#include <mlpack/core/util/param_data.hpp>
+
+namespace mlpack {
+namespace bindings {
+namespace markdown {
+
+/**
+ * Get the parameter name for a type that has no special handling.
+ */
+template<typename T>
+std::string GetPrintableParamName(
+    const util::ParamData& data,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type* = 0,
+    const typename boost::disable_if<data::HasSerialize<T>>::type* = 0,
+    const typename boost::disable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type* = 0);
+
+/**
+ * Get the parameter name for a matrix type (where the user has to pass the file
+ * that holds the matrix).
+ */
+template<typename T>
+std::string GetPrintableParamName(
+    const util::ParamData& data,
+    const typename boost::enable_if<arma::is_arma_type<T>>::type* = 0);
+
+/**
+ * Get the parameter name for a serializable model type (where the user has to
+ * pass the file that holds the matrix).
+ */
+template<typename T>
+std::string GetPrintableParamName(
+    const util::ParamData& data,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type* = 0,
+    const typename boost::enable_if<data::HasSerialize<T>>::type* = 0);
+
+/**
+ * Get the parameter name for a mapped matrix type (where the user has to pass
+ * the file that holds the matrix).
+ */
+template<typename T>
+std::string GetPrintableParamName(
+    const util::ParamData& data,
+    const typename boost::enable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type* = 0);
+
+/**
+ * Get the parameter's name as seen by the user.
+ */
+template<typename T>
+void GetPrintableParamName(
+    const util::ParamData& d,
+    const void* /* input */,
+    void* output)
+{
+  *((std::string*) output) =
+      GetPrintableParamName<typename std::remove_pointer<T>::type>(d);
+}
+
+} // namespace markdown
+} // namespace bindings
+} // namespace mlpack
+
+// Include implementation.
+#include "get_printable_param_name_impl.hpp"
+
+#endif
diff --git a/src/mlpack/bindings/markdown/get_printable_param_name_impl.hpp b/src/mlpack/bindings/markdown/get_printable_param_name_impl.hpp
new file mode 100644
index 000000000..41b9c2972
--- /dev/null
+++ b/src/mlpack/bindings/markdown/get_printable_param_name_impl.hpp
@@ -0,0 +1,79 @@
+/**
+ * @file get_printable_param_name_impl.hpp
+ * @author Ryan Curtin
+ *
+ * Return the parameter name that the user would specify on the command line,
+ * with different behavior for different parameter types.
+ *
+ * mlpack is free software; you may redistribute it and/or modify it under the
+ * terms of the 3-clause BSD license.  You should have received a copy of the
+ * 3-clause BSD license along with mlpack.  If not, see
+ * http://www.opensource.org/licenses/BSD-3-Clause for more information.
+ */
+#ifndef MLPACK_BINDINGS_MARKDOWN_GET_PRINTABLE_PARAM_NAME_IMPL_HPP
+#define MLPACK_BINDINGS_MARKDOWN_GET_PRINTABLE_PARAM_NAME_IMPL_HPP
+
+#include <mlpack/prereqs.hpp>
+#include <mlpack/core/util/param_data.hpp>
+
+namespace mlpack {
+namespace bindings {
+namespace markdown {
+
+/**
+ * Get the parameter name for a type that has no special handling.
+ */
+template<typename T>
+std::string GetPrintableParamName(
+    const util::ParamData& data,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type*,
+    const typename boost::disable_if<data::HasSerialize<T>>::type*,
+    const typename boost::disable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type*)
+{
+  return "--" + data.name;
+}
+
+/**
+ * Get the parameter name for a matrix type (where the user has to pass the file
+ * that holds the matrix).
+ */
+template<typename T>
+std::string GetPrintableParamName(
+    const util::ParamData& data,
+    const typename boost::enable_if<arma::is_arma_type<T>>::type*)
+{
+  return "--" + data.name + "_file";
+}
+
+/**
+ * Get the parameter name for a serializable model type (where the user has to
+ * pass the file that holds the matrix).
+ */
+template<typename T>
+std::string GetPrintableParamName(
+    const util::ParamData& data,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type*,
+    const typename boost::enable_if<data::HasSerialize<T>>::type*)
+{
+  return "--" + data.name + "_file";
+}
+
+/**
+ * Get the parameter name for a mapped matrix type (where the user has to pass
+ * the file that holds the matrix).
+ */
+template<typename T>
+std::string GetPrintableParamName(
+    const util::ParamData& data,
+    const typename boost::enable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type*)
+{
+  return "--" + data.name + "_file";
+}
+
+} // namespace markdown
+} // namespace bindings
+} // namespace mlpack
+
+#endif
diff --git a/src/mlpack/bindings/markdown/get_printable_param_value.hpp b/src/mlpack/bindings/markdown/get_printable_param_value.hpp
new file mode 100644
index 000000000..d58bc93bf
--- /dev/null
+++ b/src/mlpack/bindings/markdown/get_printable_param_value.hpp
@@ -0,0 +1,88 @@
+/**
+ * @file get_printable_param_value.hpp
+ * @author Ryan Curtin
+ *
+ * Given a parameter value, print what the user might actually specify on the
+ * command line.  Basically this adds ".csv" to types where data must be loaded.
+ *
+ * mlpack is free software; you may redistribute it and/or modify it under the
+ * terms of the 3-clause BSD license.  You should have received a copy of the
+ * 3-clause BSD license along with mlpack.  If not, see
+ * http://www.opensource.org/licenses/BSD-3-Clause for more information.
+ */
+#ifndef MLPACK_BINDINGS_MARKDOWN_GET_PRINTABLE_PARAM_VALUE_HPP
+#define MLPACK_BINDINGS_MARKDOWN_GET_PRINTABLE_PARAM_VALUE_HPP
+
+#include <mlpack/prereqs.hpp>
+#include <mlpack/core/util/param_data.hpp>
+
+namespace mlpack {
+namespace bindings {
+namespace markdown {
+
+/**
+ * Get the parameter name for a type that has no special handling.
+ */
+template<typename T>
+std::string GetPrintableParamValue(
+    const util::ParamData& data,
+    const std::string& value,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type* = 0,
+    const typename boost::disable_if<data::HasSerialize<T>>::type* = 0,
+    const typename boost::disable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type* = 0);
+
+/**
+ * Get the parameter name for a matrix type (where the user has to pass the file
+ * that holds the matrix).
+ */
+template<typename T>
+std::string GetPrintableParamValue(
+    const util::ParamData& data,
+    const std::string& value,
+    const typename boost::enable_if<arma::is_arma_type<T>>::type* = 0);
+
+/**
+ * Get the parameter name for a serializable model type (where the user has to
+ * pass the file that holds the matrix).
+ */
+template<typename T>
+std::string GetPrintableParamValue(
+    const util::ParamData& data,
+    const std::string& value,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type* = 0,
+    const typename boost::enable_if<data::HasSerialize<T>>::type* = 0);
+
+/**
+ * Get the parameter name for a mapped matrix type (where the user has to pass
+ * the file that holds the matrix).
+ */
+template<typename T>
+std::string GetPrintableParamValue(
+    const util::ParamData& data,
+    const std::string& value,
+    const typename boost::enable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type* = 0);
+
+/**
+ * Get the parameter's name as seen by the user.
+ */
+template<typename T>
+void GetPrintableParamValue(
+    const util::ParamData& d,
+    const void* input,
+    void* output)
+{
+  *((std::string*) output) =
+      GetPrintableParamValue<typename std::remove_pointer<T>::type>(d,
+      *((std::string*) input));
+}
+
+} // namespace markdown
+} // namespace bindings
+} // namespace mlpack
+
+// Include implementation.
+#include "get_printable_param_value_impl.hpp"
+
+#endif
diff --git a/src/mlpack/bindings/markdown/get_printable_param_value_impl.hpp b/src/mlpack/bindings/markdown/get_printable_param_value_impl.hpp
new file mode 100644
index 000000000..9fce0559b
--- /dev/null
+++ b/src/mlpack/bindings/markdown/get_printable_param_value_impl.hpp
@@ -0,0 +1,84 @@
+/**
+ * @file get_printable_param_value_impl.hpp
+ * @author Ryan Curtin
+ *
+ * Return the parameter value that the user would specify on the command line
+ * depending on the type of the option.  Basically this adds ".csv" to types
+ * that need to be loaded.
+ *
+ * mlpack is free software; you may redistribute it and/or modify it under the
+ * terms of the 3-clause BSD license.  You should have received a copy of the
+ * 3-clause BSD license along with mlpack.  If not, see
+ * http://www.opensource.org/licenses/BSD-3-Clause for more information.
+ */
+#ifndef MLPACK_BINDINGS_MARKDOWN_GET_PRINTABLE_PARAM_VALUE_IMPL_HPP
+#define MLPACK_BINDINGS_MARKDOWN_GET_PRINTABLE_PARAM_VALUE_IMPL_HPP
+
+#include <mlpack/prereqs.hpp>
+#include <mlpack/core/util/param_data.hpp>
+
+namespace mlpack {
+namespace bindings {
+namespace markdown {
+
+/**
+ * Get the parameter name for a type that has no special handling.
+ */
+template<typename T>
+std::string GetPrintableParamValue(
+    const util::ParamData& /* data */,
+    const std::string& input,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type*,
+    const typename boost::disable_if<data::HasSerialize<T>>::type*,
+    const typename boost::disable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type*)
+{
+  return input;
+}
+
+/**
+ * Get the parameter name for a matrix type (where the user has to pass the file
+ * that holds the matrix).
+ */
+template<typename T>
+std::string GetPrintableParamValue(
+    const util::ParamData& /* data */,
+    const std::string& input,
+    const typename boost::enable_if<arma::is_arma_type<T>>::type*)
+{
+  return input + ".csv";
+}
+
+/**
+ * Get the parameter name for a serializable model type (where the user has to
+ * pass the file that holds the matrix).
+ */
+template<typename T>
+std::string GetPrintableParamValue(
+    const util::ParamData& /* data */,
+    const std::string& input,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type*,
+    const typename boost::enable_if<data::HasSerialize<T>>::type*)
+{
+  return input + ".bin";
+}
+
+/**
+ * Get the parameter name for a mapped matrix type (where the user has to pass
+ * the file that holds the matrix).
+ */
+template<typename T>
+std::string GetPrintableParamValue(
+    const util::ParamData& /* data */,
+    const std::string& input,
+    const typename boost::enable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type*)
+{
+  return input + ".arff";
+}
+
+} // namespace markdown
+} // namespace bindings
+} // namespace mlpack
+
+#endif
diff --git a/src/mlpack/bindings/markdown/get_printable_type.hpp b/src/mlpack/bindings/markdown/get_printable_type.hpp
new file mode 100644
index 000000000..2b292b937
--- /dev/null
+++ b/src/mlpack/bindings/markdown/get_printable_type.hpp
@@ -0,0 +1,62 @@
+/**
+ * @file get_printable_type.hpp
+ * @author Ryan Curtin
+ *
+ * Get the printable type of the parameter.  This depends on
+ * BindingInfo::Language() to choose which language to return the type for.
+ */
+#ifndef MLPACK_BINDINGS_MARKDOWN_GET_PRINTABLE_TYPE_HPP
+#define MLPACK_BINDINGS_MARKDOWN_GET_PRINTABLE_TYPE_HPP
+
+#include "binding_info.hpp"
+
+#include <mlpack/bindings/cli/get_printable_type.hpp>
+#include <mlpack/bindings/python/get_printable_type.hpp>
+
+namespace mlpack {
+namespace bindings {
+namespace markdown {
+
+/**
+ * Print the type of a parameter into the output string.  The type printed
+ * depends on the current setting of BindingInfo::Language().
+ */
+template<typename T>
+void GetPrintableType(const util::ParamData& data,
+                      const void* /* input */,
+                      void* output)
+{
+  if (BindingInfo::Language() == "cli")
+  {
+    *((std::string*) output) =
+        cli::GetPrintableType<typename std::remove_pointer<T>::type>(data);
+  }
+  else if (BindingInfo::Language() == "python")
+  {
+    *((std::string*) output) =
+        python::GetPrintableType<typename std::remove_pointer<T>::type>(data);
+  }
+  else
+  {
+    throw std::invalid_argument("GetPrintableType(): unknown "
+        "BindingInfo::Language(): " + BindingInfo::Language() + "!");
+  }
+}
+
+/**
+ * Print the type of a parameter.  The type printed depends on the current
+ * setting of BindingInfo::Language().
+ */
+template<typename T>
+std::string GetPrintableType(const util::ParamData& data)
+{
+  std::string output;
+  GetPrintableType<T>(data, (void*) NULL, (void*) &output);
+  return output;
+}
+
+} // namespace markdown
+} // namespace bindings
+} // namespace mlpack
+
+#endif
diff --git a/src/mlpack/bindings/markdown/is_serializable.hpp b/src/mlpack/bindings/markdown/is_serializable.hpp
new file mode 100644
index 000000000..b129487b9
--- /dev/null
+++ b/src/mlpack/bindings/markdown/is_serializable.hpp
@@ -0,0 +1,63 @@
+/**
+ * @file is_serializable.hpp
+ * @author Ryan Curtin
+ *
+ * Return a bool noting whether or not a parameter is serializable.
+ */
+#ifndef MLPACK_BINDINGS_MARKDOWN_IS_SERIALIZABLE_HPP
+#define MLPACK_BINDINGS_MARKDOWN_IS_SERIALIZABLE_HPP
+
+#include <mlpack/prereqs.hpp>
+
+namespace mlpack {
+namespace bindings {
+namespace markdown {
+
+/**
+ * Return false, because the type is not serializable.
+ */
+template<typename T>
+bool IsSerializable(
+    const typename boost::disable_if<data::HasSerialize<T>>::type* = 0)
+{
+  return false;
+}
+
+/**
+ * Return false, because even though the type is serializable, it is an
+ * Armadillo type not an mlpack model.
+ */
+template<typename T>
+bool IsSerializable(
+    const typename boost::enable_if<arma::is_arma_type<T>>::type* = 0)
+{
+  return false;
+}
+
+/**
+ * Return true, because the type is serializable.
+ */
+template<typename T>
+bool IsSerializable(
+    const typename boost::enable_if<data::HasSerialize<T>>::type* = 0,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type* = 0)
+{
+  return true;
+}
+
+/**
+ * Return whether or not the type is serializable.
+ */
+template<typename T>
+void IsSerializable(const util::ParamData& /* data */,
+                    const void* /* input */,
+                    void* output)
+{
+  *((bool*) output) = IsSerializable<typename std::remove_pointer<T>::type>();
+}
+
+} // namespace markdown
+} // namespace bindings
+} // namespace mlpack
+
+#endif
diff --git a/src/mlpack/bindings/markdown/md_option.hpp b/src/mlpack/bindings/markdown/md_option.hpp
new file mode 100644
index 000000000..dc728c4e9
--- /dev/null
+++ b/src/mlpack/bindings/markdown/md_option.hpp
@@ -0,0 +1,109 @@
+/**
+ * @file md_option.hpp
+ * @author Ryan Curtin
+ *
+ * The Markdown option type.
+ *
+ * mlpack is free software; you may redistribute it and/or modify it under the
+ * terms of the 3-clause BSD license.  You should have received a copy of the
+ * 3-clause BSD license along with mlpack.  If not, see
+ * http://www.opensource.org/licenses/BSD-3-Clause for more information.
+ */
+#ifndef MLPACK_BINDINGS_MARKDOWN_MD_OPTION_HPP
+#define MLPACK_BINDINGS_MARKDOWN_MD_OPTION_HPP
+
+#include <mlpack/core/util/param_data.hpp>
+#include <mlpack/core/util/cli.hpp>
+#include "default_param.hpp"
+#include "get_param.hpp"
+#include "get_printable_param.hpp"
+#include "get_printable_param_name.hpp" // For cli bindings.
+#include "get_printable_param_value.hpp" // For cli bindings.
+#include "get_printable_type.hpp"
+#include "is_serializable.hpp"
+
+namespace mlpack {
+namespace bindings {
+namespace markdown {
+
+/**
+ * The Markdown option class.
+ */
+template<typename T>
+class MDOption
+{
+ public:
+  /**
+   * Construct an MDOption object.  When constructed, it will register itself
+   * with CLI. The testName parameter is not used and added for compatibility
+   * reasons.
+   */
+  MDOption(const T defaultValue,
+           const std::string& identifier,
+           const std::string& description,
+           const std::string& alias,
+           const std::string& cppName,
+           const bool required = false,
+           const bool input = true,
+           const bool noTranspose = false,
+           const std::string& bindingName = "")
+  {
+    // Create the ParamData object to give to CLI.
+    util::ParamData data;
+
+    data.desc = description;
+    data.name = identifier;
+    data.tname = TYPENAME(T);
+    data.alias = alias[0];
+    data.wasPassed = false;
+    data.noTranspose = noTranspose;
+    data.required = required;
+    data.input = input;
+    data.loaded = false;
+    // Several options from Python and CLI bindings are persistent.
+    if (identifier == "verbose" || identifier == "copy_all_inputs" ||
+        identifier == "help" || identifier == "info" || identifier == "version")
+      data.persistent = true;
+    else
+      data.persistent = false;
+    data.cppType = cppName;
+
+    // Every parameter we'll get from Markdown will have the correct type.
+    data.value = boost::any(defaultValue);
+
+    // Restore the parameters for this program.
+    if (identifier != "verbose" && identifier != "copy_all_inputs")
+      CLI::RestoreSettings(bindingName, false);
+
+    // Set the function pointers that we'll need.  Most of these simply delegate
+    // to the current binding type's implementation.  Any new language will need
+    // to have all of these implemented, and the Markdown implementation will
+    // need to properly delegate.
+    CLI::GetSingleton().functionMap[data.tname]["DefaultParam"] =
+        &DefaultParam<T>;
+    CLI::GetSingleton().functionMap[data.tname]["GetParam"] = &GetParam<T>;
+    CLI::GetSingleton().functionMap[data.tname]["GetPrintableParam"] =
+        &GetPrintableParam<T>;
+    CLI::GetSingleton().functionMap[data.tname]["GetPrintableParamName"] =
+        &GetPrintableParamName<T>;
+    CLI::GetSingleton().functionMap[data.tname]["GetPrintableParamValue"] =
+        &GetPrintableParamValue<T>;
+    CLI::GetSingleton().functionMap[data.tname]["GetPrintableType"] =
+        &GetPrintableType<T>;
+    CLI::GetSingleton().functionMap[data.tname]["IsSerializable"] =
+        &IsSerializable<T>;
+
+    // Add the option.
+    CLI::Add(std::move(data));
+    if (identifier != "verbose" && identifier != "copy_all_inputs" &&
+        identifier != "help" && identifier != "info" && identifier != "version")
+      CLI::StoreSettings(bindingName);
+    CLI::ClearSettings();
+  }
+};
+
+} // namespace markdown
+} // namespace bindings
+} // namespace mlpack
+
+#endif
diff --git a/src/mlpack/bindings/markdown/print_doc_functions.hpp b/src/mlpack/bindings/markdown/print_doc_functions.hpp
new file mode 100644
index 000000000..2430fa512
--- /dev/null
+++ b/src/mlpack/bindings/markdown/print_doc_functions.hpp
@@ -0,0 +1,109 @@
+/**
+ * @file print_doc_functions.hpp
+ * @author Ryan Curtin
+ *
+ * This file wraps the different printing functionality of different binding
+ * types.  If a new binding type is added, this code will need to be modified so
+ * that Markdown can be printed.
+ *
+ * mlpack is free software; you may redistribute it and/or modify it under the
+ * terms of the 3-clause BSD license.  You should have received a copy of the
+ * 3-clause BSD license along with mlpack.  If not, see
+ * http://www.opensource.org/licenses/BSD-3-Clause for more information.
+ */
+#ifndef MLPACK_BINDINGS_MARKDOWN_PRINT_DOC_FUNCTIONS_HPP
+#define MLPACK_BINDINGS_MARKDOWN_PRINT_DOC_FUNCTIONS_HPP
+
+#include <mlpack/prereqs.hpp>
+
+namespace mlpack {
+namespace bindings {
+namespace markdown {
+
+/**
+ * Given the name of the binding, print the name for the current language (as
+ * given by BindingInfo).
+ */
+inline std::string GetBindingName(const std::string& bindingName);
+
+/**
+ * Print the name of the given language.
+ */
+inline std::string PrintLanguage(const std::string& language);
+
+/**
+ * Print any imports that need to be done before using the binding.
+ */
+inline std::string PrintImport(const std::string& bindingName);
+
+/**
+ * Print any special information about output options.
+ */
+inline std::string PrintOutputOptionInfo(const std::string& language);
+
+/**
+ * Print details about the different types for a language.
+ */
+inline std::string PrintTypeDocs();
+
+/**
+ * Given a parameter type, print the corresponding value.
+ */
+template<typename T>
+inline std::string PrintValue(const T& value, bool quotes);
+
+/**
+ * Print the default value of an option, unless it is required (in which case
+ * Markdown italicized '--' is printed).
+ */
+inline std::string PrintDefault(const std::string& paramName);
+
+/**
+ * Print a dataset type parameter (add .csv and return).
+ */
+inline std::string PrintDataset(const std::string& dataset);
+
+/**
+ * Print a model type parameter (add .bin and return).
+ */
+inline std::string PrintModel(const std::string& model);
+
+/**
+ * Given a program name and arguments for it, print what its invocation would
+ * be.
+ */
+template<typename... Args>
+std::string ProgramCall(const std::string& programName, Args... args);
+
+/**
+ * Given a program name, print a call assuming that all arguments are specified.
+ */
+inline std::string ProgramCall(const std::string& programName);
+
+/**
+ * Print what a user would type to invoke the given option name.  Note that the
+ * name *must* exist in the CLI module.  (Note that because of the way
+ * ProgramInfo is structured, this doesn't mean that all of the PARAM_*()
+ * declarataions need to come before the PROGRAM_INFO() declaration.)
+ */
+inline std::string ParamString(const std::string& paramName);
+
+/**
+ * Print the user-encountered type of an option.
+ */
+inline std::string ParamType(const util::ParamData& d);
+
+/**
+ * Return whether or not a runtime check on parameters should be ignored.
+ */
+template<typename T>
+inline bool IgnoreCheck(const T& t);
+
+} // namespace markdown
+} // namespace bindings
+} // namespace mlpack
+
+// Include implementation.
+#include "print_doc_functions_impl.hpp"
+
+#endif
diff --git a/src/mlpack/bindings/markdown/print_doc_functions_impl.hpp b/src/mlpack/bindings/markdown/print_doc_functions_impl.hpp
new file mode 100644
index 000000000..13359441d
--- /dev/null
+++ b/src/mlpack/bindings/markdown/print_doc_functions_impl.hpp
@@ -0,0 +1,540 @@
+/**
+ * @file print_doc_functions_impl.hpp
+ * @author Ryan Curtin
+ *
+ * Call out to different printing functionality for different binding languages.
+ * If a new binding is added, this code must be modified.
+ *
+ * mlpack is free software; you may redistribute it and/or modify it under the
+ * terms of the 3-clause BSD license.  You should have received a copy of the
+ * 3-clause BSD license along with mlpack.  If not, see
+ * http://www.opensource.org/licenses/BSD-3-Clause for more information.
+ */
+#ifndef MLPACK_BINDINGS_MARKDOWN_PRINT_DOC_FUNCTIONS_IMPL_HPP
+#define MLPACK_BINDINGS_MARKDOWN_PRINT_DOC_FUNCTIONS_IMPL_HPP
+
+#include "print_doc_functions.hpp"
+#include "binding_info.hpp"
+#include "print_type_doc.hpp"
+#include "get_printable_type.hpp"
+
+#include <mlpack/bindings/cli/print_doc_functions.hpp>
+#include <mlpack/bindings/python/print_doc_functions.hpp>
+
+namespace mlpack {
+namespace bindings {
+namespace markdown {
+
+/**
+ * Given the name of the binding, print the name for the current language (as
+ * given by BindingInfo).
+ */
+inline std::string GetBindingName(const std::string& bindingName)
+{
+  if (BindingInfo::Language() == "cli")
+  {
+    return cli::GetBindingName(bindingName);
+  }
+  else if (BindingInfo::Language() == "python")
+  {
+    return python::GetBindingName(bindingName);
+  }
+  else
+  {
+    throw std::invalid_argument("PrintValue(): unknown "
+        "BindingInfo::Language(): " + BindingInfo::Language() + "!");
+  }
+}
+
+/**
+ * Print the name of the given language.
+ */
+inline std::string PrintLanguage(const std::string& language)
+{
+  if (language == "cli")
+  {
+    return "CLI";
+  }
+  else if (language == "python")
+  {
+    return "Python";
+  }
+  else
+  {
+    throw std::invalid_argument("PrintLanguage(): unknown "
+        "BindingInfo::Language(): " + BindingInfo::Language() + "!");
+  }
+}
+
+/**
+ * Print any imports that need to be done before using the binding.
+ */
+inline std::string PrintImport(const std::string& bindingName)
+{
+  if (BindingInfo::Language() == "cli")
+  {
+    return cli::PrintImport(bindingName);
+  }
+  else if (BindingInfo::Language() == "python")
+  {
+    return python::PrintImport(bindingName);
+  }
+  else
+  {
+    throw std::invalid_argument("PrintImport(): unknown "
+        "BindingInfo::Language(): " + BindingInfo::Language() + "!");
+  }
+}
+
+/**
+ * Print any special information about output options.
+ */
+inline std::string PrintOutputOptionInfo()
+{
+  if (BindingInfo::Language() == "cli")
+  {
+    return cli::PrintOutputOptionInfo();
+  }
+  else if (BindingInfo::Language() == "python")
+  {
+    return python::PrintOutputOptionInfo();
+  }
+  else
+  {
+    throw std::invalid_argument("PrintOutputOptionInfo(): unknown "
+        "BindingInfo::Language(): " + BindingInfo::Language() + "!");
+  }
+}
+
+namespace priv {
+
+// We'll need a fake class for printing model type documentation.
+class mlpackModel
+{
+ public:
+  // Fake serialization to make SFINAE work right for this type.
+  template<typename Archive>
+  void serialize(Archive&, const unsigned int) {}
+};
+
+} // namespace priv
+
+// Utility function that returns the first word (as delimited by spaces) of a
+// string.
+inline std::string ToUnderscores(const std::string& str)
+{
+  std::string ret(str);
+  std::replace(ret.begin(), ret.end(), ' ', '_');
+  return ret;
+}
+
+/**
+ * Print details about the different types for a language.
+ */
+inline std::string PrintTypeDocs()
+{
+  std::ostringstream oss;
+  oss << "<div id=\"" << BindingInfo::Language()
+      << "\" class=\"language-types\" markdown=\"1\">" << std::endl;
+  oss << "## data formats" << std::endl;
+  oss << "{: .language-types-h2 #" << BindingInfo::Language()
+      << "_data-formats }" << std::endl;
+  oss << std::endl;
+
+  // Iterate through each of the types that we care about.
+  oss << "mlpack bindings for " << PrintLanguage(BindingInfo::Language())
+      << " take and return a restricted set of types, for simplicity.  These "
+      << "include primitive types, matrix/vector types, categorical matrix "
+      << "types, and model types.  Each type is detailed below." << std::endl;
+  oss << std::endl;
+
+  // Create fake ParamData to pass around.
+  util::ParamData data;
+  data.desc = "fake";
+  data.name = "fake";
+  data.tname = std::string(typeid(int).name());
+  data.cppType = "int";
+  data.alias = 'f';
+  data.wasPassed = false;
+  data.noTranspose = true;
+  data.required = false;
+  data.input = true;
+  data.loaded = false;
+  data.persistent = false;
+  data.value = boost::any(int(0));
+
+  std::string type = GetPrintableType<int>(data);
+  oss << " - `" << type << "`{: #doc_" << BindingInfo::Language() << "_"
+      << ToUnderscores(type) << " }: " << PrintTypeDoc<int>(data) << std::endl;
+
+  data.tname = std::string(typeid(double).name());
+  data.cppType = "double";
+  data.value = boost::any(double(0.0));
+
+  type = GetPrintableType<double>(data);
+  oss << " - `" << type << "`{: #doc_" << BindingInfo::Language() << "_"
+      << ToUnderscores(type) << " }: " << PrintTypeDoc<double>(data)
+      << std::endl;
+
+  data.tname = std::string(typeid(bool).name());
+  data.cppType = "double";
+  data.value = boost::any(bool(0.0));
+
+  type = GetPrintableType<bool>(data);
+  oss << " - `" << type << "`{: #doc_" << BindingInfo::Language() << "_"
+      << ToUnderscores(type) << " }: " << PrintTypeDoc<bool>(data) << std::endl;
+
+  data.tname = std::string(typeid(std::string).name());
+  data.cppType = "std::string";
+  data.value = boost::any(std::string(""));
+
+  type = GetPrintableType<std::string>(data);
+  oss << " - `" << type << "`{: #doc_" << BindingInfo::Language() << "_"
+      << ToUnderscores(type) << " }: " << PrintTypeDoc<std::string>(data)
+      << std::endl;
+
+  data.tname = std::string(typeid(std::vector<int>).name());
+  data.cppType = "std::vector<int>";
+  data.value = boost::any(std::vector<int>());
+
+  type = GetPrintableType<std::vector<int>>(data);
+  oss << " - `" << type << "`{: #doc_" << BindingInfo::Language() << "_"
+      << ToUnderscores(type) << " }: " << PrintTypeDoc<std::vector<int>>(data)
+      << std::endl;
+
+  data.tname = std::string(typeid(std::vector<std::string>).name());
+  data.cppType = "std::vector<std::string>";
+  data.value = boost::any(std::vector<std::string>());
+
+  type = GetPrintableType<std::vector<std::string>>(data);
+  oss << " - `" << type << "`{: " << "#doc_" << BindingInfo::Language() << "_"
+      << ToUnderscores(type) << " }: "
+      << PrintTypeDoc<std::vector<std::string>>(data) << std::endl;
+
+  data.tname = std::string(typeid(arma::mat).name());
+  data.cppType = "arma::mat";
+  data.value = boost::any(arma::mat());
+
+  type = GetPrintableType<arma::mat>(data);
+  oss << " - `" << type << "`{: #doc_" << BindingInfo::Language() << "_"
+      << ToUnderscores(type) << " }: " << PrintTypeDoc<arma::mat>(data)
+      << std::endl;
+
+  data.tname = std::string(typeid(arma::Mat<size_t>).name());
+  data.cppType = "arma::Mat<size_t>";
+  data.value = boost::any(arma::Mat<size_t>());
+
+  type = GetPrintableType<arma::Mat<size_t>>(data);
+  oss << " - `" << type << "`{: #doc_" << BindingInfo::Language() << "_"
+      << ToUnderscores(type) << " }: " << PrintTypeDoc<arma::Mat<size_t>>(data)
+      << std::endl;
+
+  data.tname = std::string(typeid(arma::rowvec).name());
+  data.cppType = "arma::rowvec";
+  data.value = boost::any(arma::rowvec());
+  const std::string& rowType = GetPrintableType<arma::rowvec>(data);
+
+  oss << " - `" << rowType << "`{: #doc_" << BindingInfo::Language() << "_"
+      << ToUnderscores(rowType) << " }: " << PrintTypeDoc<arma::rowvec>(data)
+      << std::endl;
+
+  data.tname = std::string(typeid(arma::Row<size_t>).name());
+  data.cppType = "arma::Row<size_t>";
+  data.value = boost::any(arma::Row<size_t>());
+  const std::string& urowType = GetPrintableType<arma::Row<size_t>>(data);
+
+  oss << " - `" << urowType << "`{: #doc_" << BindingInfo::Language() << "_"
+      << ToUnderscores(urowType) << " }: "
+      << PrintTypeDoc<arma::Row<size_t>>(data)
+      << std::endl;
+
+  data.tname = std::string(typeid(arma::vec).name());
+  data.cppType = "arma::vec";
+  data.value = boost::any(arma::vec());
+  const std::string& colType = GetPrintableType<arma::vec>(data);
+
+  // For some languages there is no distinction between column and row vectors.
+  // If that is the case, then don't print both.
+  if (colType != rowType)
+  {
+    oss << " - `" << colType << "`{: #doc_" << BindingInfo::Language() << "_"
+        << ToUnderscores(colType) << " }: " << PrintTypeDoc<arma::vec>(data)
+        << std::endl;
+  }
+
+  data.tname = std::string(typeid(arma::Col<size_t>).name());
+  data.cppType = "arma::Col<size_t>";
+  data.value = boost::any(arma::Col<size_t>());
+  const std::string& ucolType = GetPrintableType<arma::Col<size_t>>(data);
+
+  // For some languages there is no distinction between column and row vectors.
+  // If that is the case, then don't print both.
+  if (ucolType != urowType)
+  {
+    oss << " - `" << ucolType << "`{ #doc_" << BindingInfo::Language() << "_"
+        << ToUnderscores(ucolType) << " }: "
+        << PrintTypeDoc<arma::Col<size_t>>(data) << std::endl;
+  }
+
+  data.tname =
+      std::string(typeid(std::tuple<data::DatasetInfo, arma::mat>).name());
+  data.cppType = "std::tuple<data::DatasetInfo, arma::mat>";
+  data.value = boost::any(std::tuple<data::DatasetInfo, arma::mat>());
+
+  type = GetPrintableType<std::tuple<data::DatasetInfo, arma::mat>>(data);
+  oss << " - `" << type << "`{: #doc_" << BindingInfo::Language() << "_"
+      << ToUnderscores(type) << " }: "
+      << PrintTypeDoc<std::tuple<data::DatasetInfo, arma::mat>>(data)
+      << std::endl;
+
+  data.tname = std::string(typeid(priv::mlpackModel).name());
+  data.cppType = "mlpackModel";
+  data.value = boost::any(new priv::mlpackModel());
+
+  type = GetPrintableType<priv::mlpackModel*>(data);
+  oss << " - `" << type << "`{: #doc_" << BindingInfo::Language()
+      << "_model }: " << PrintTypeDoc<priv::mlpackModel*>(data) << std::endl;
+
+  // Clean up memory.
+  delete boost::any_cast<priv::mlpackModel*>(data.value);
+
+  oss << std::endl << "</div>" << std::endl;
+
+  return oss.str();
+}
+
+/**
+ * Given a parameter type, print the corresponding value.
+ */
+template<typename T>
+inline std::string PrintValue(const T& value, bool quotes)
+{
+  std::string result;
+  if (BindingInfo::Language() == "cli")
+  {
+    result = cli::PrintValue(value, quotes);
+  }
+  else if (BindingInfo::Language() == "python")
+  {
+    result = python::PrintValue(value, quotes);
+  }
+  else
+  {
+    throw std::invalid_argument("PrintValue(): unknown "
+        "BindingInfo::Language(): " + BindingInfo::Language() + "!");
+  }
+
+  return "`" + result + "`";
+}
+
+/**
+ * Given a parameter name, print its corresponding default value.
+ */
+inline std::string PrintDefault(const std::string& paramName)
+{
+  if (CLI::Parameters().count(paramName) == 0)
+    throw std::invalid_argument("unknown parameter" + paramName + "!");
+
+  const util::ParamData& d = CLI::Parameters()[paramName];
+
+  std::ostringstream oss;
+
+  if (d.required)
+  {
+    oss << "**--**";
+  }
+  else
+  {
+    if (BindingInfo::Language() == "cli")
+    {
+      oss << cli::PrintDefault(paramName);
+    }
+    else if (BindingInfo::Language() == "python")
+    {
+      oss << python::PrintDefault(paramName);
+    }
+    else
+    {
+      throw std::invalid_argument("PrintDefault: unknown "
+          "BindingInfo::Language(): " + BindingInfo::Language() + "!");
+    }
+  }
+
+  return oss.str();
+}
+
+/**
+ * Print a dataset type parameter (add .csv and return).
+ */
+inline std::string PrintDataset(const std::string& dataset)
+{
+  std::string result;
+  if (BindingInfo::Language() == "cli")
+  {
+    result = cli::PrintDataset(dataset);
+  }
+  else if (BindingInfo::Language() == "python")
+  {
+    result = python::PrintDataset(dataset);
+  }
+  else
+  {
+    throw std::invalid_argument("PrintDataset(): unknown "
+        "BindingInfo::Language(): " + BindingInfo::Language() + "!");
+  }
+
+  return "`" + result + "`";
+}
+
+/**
+ * Print a model type parameter.
+ */
+inline std::string PrintModel(const std::string& model)
+{
+  std::string result;
+  if (BindingInfo::Language() == "cli")
+  {
+    result = cli::PrintModel(model);
+  }
+  else if (BindingInfo::Language() == "python")
+  {
+    result = python::PrintModel(model);
+  }
+  else
+  {
+    throw std::invalid_argument("PrintModel(): unknown "
+        "BindingInfo::Language(): " + BindingInfo::Language() + "!");
+  }
+
+  return "`" + result + "`";
+}
+
+/**
+ * Given a program name and arguments for it, print what its invocation would
+ * be.
+ */
+template<typename... Args>
+std::string ProgramCall(const std::string& programName, Args... args)
+{
+  std::string s = "```";
+  if (BindingInfo::Language() == "cli")
+  {
+    s += "bash\n";
+    s += cli::ProgramCall(programName, args...);
+  }
+  else if (BindingInfo::Language() == "python")
+  {
+    s += "python\n";
+    s += python::ProgramCall(programName, args...);
+  }
+  else
+  {
+    throw std::invalid_argument("ProgramCall(): unknown "
+        "BindingInfo::Language(): " + BindingInfo::Language() + "!");
+  }
+  s += "\n```";
+  return s;
+}
+
+/**
+ * Given a program name, print a call assuming that all arguments are specified.
+ */
+inline std::string ProgramCall(const std::string& programName)
+{
+  std::string s = "```";
+  if (BindingInfo::Language() == "cli")
+  {
+    s += "bash\n";
+    std::string import = PrintImport(GetBindingName(programName));
+    if (import.size() > 0)
+      s += "$ " + import + "\n";
+    s += cli::ProgramCall(programName);
+  }
+  else if (BindingInfo::Language() == "python")
+  {
+    s += "python\n";
+    std::string import = PrintImport(GetBindingName(programName));
+    if (import.size() > 0)
+      s += ">>> " + import + "\n";
+    s += python::ProgramCall(programName);
+  }
+  else
+  {
+    throw std::invalid_argument("ProgramCall(): unknown "
+        "BindingInfo::Language(): " + BindingInfo::Language() + "!");
+  }
+  s += "\n```\n";
+  return s;
+}
+
+/**
+ * Print what a user would type to invoke the given option name.  Note that the
+ * name *must* exist in the CLI module.  (Note that because of the way
+ * ProgramInfo is structured, this doesn't mean that all of the PARAM_*()
+ * declarataions need to come before the PROGRAM_INFO() declaration.)
+ */
+inline std::string ParamString(const std::string& paramName)
+{
+  // These functions always put a '' around the parameter, so we will skip that
+  // bit.
+  std::string s;
+  if (BindingInfo::Language() == "cli")
+  {
+    // The CLI bindings put a '' around the parameter, so skip that...
+    s = cli::ParamString(paramName);
+  }
+  else if (BindingInfo::Language() == "python")
+  {
+    s = python::ParamString(paramName);
+  }
+  else
+  {
+    throw std::invalid_argument("ParamString(): unknown "
+        "BindingInfo::Language(): " + BindingInfo::Language() + "!");
+  }
+
+  return "`" + s.substr(1, s.size() - 2) + "`";
+}
+
+/**
+ * Print the user-encountered type of an option.
+ */
+inline std::string ParamType(const util::ParamData& d)
+{
+  std::string output;
+  CLI::GetSingleton().functionMap[d.tname]["GetPrintableType"](d, NULL,
+      &output);
+  // We want to make this a link to the type documentation.
+  std::string anchorType = output;
+  bool result;
+  CLI::GetSingleton().functionMap[d.tname]["IsSerializable"](d, NULL, &result);
+  if (result)
+    anchorType = "model";
+
+  return "[`" + output + "`](#doc_" + BindingInfo::Language() + "_" +
+      ToUnderscores(anchorType) + ")";
+}
+
+template<typename T>
+inline bool IgnoreCheck(const T& t)
+{
+  if (BindingInfo::Language() == "cli")
+  {
+    return cli::IgnoreCheck(t);
+  }
+  else if (BindingInfo::Language() == "python")
+  {
+    return python::IgnoreCheck(t);
+  }
+  else
+  {
+    throw std::invalid_argument("IgnoreCheck(): unknown "
+        "BindingInfo::Language(): " + BindingInfo::Language() + "!");
+  }
+}
+
+} // namespace markdown
+} // namespace bindings
+} // namespace mlpack
+
+#endif
diff --git a/src/mlpack/bindings/markdown/print_docs.cpp b/src/mlpack/bindings/markdown/print_docs.cpp
new file mode 100644
index 000000000..2711a8789
--- /dev/null
+++ b/src/mlpack/bindings/markdown/print_docs.cpp
@@ -0,0 +1,218 @@
+/**
+ * @file print_docs.cpp
+ * @author Ryan Curtin
+ *
+ * Implementation of functions to print Markdown from documentation.
+ */
+#include "print_docs.hpp"
+
+#include <mlpack/core/util/cli.hpp>
+#include <mlpack/core/util/program_doc.hpp>
+#include "binding_info.hpp"
+#include "print_doc_functions.hpp"
+
+// Make sure that this is defined.
+#ifndef DOXYGEN_PREFIX
+#define DOXYGEN_PREFIX "https://mlpack.org/docs/mlpack-git/doxygen/"
+#endif
+
+using namespace std;
+using namespace mlpack;
+using namespace mlpack::util;
+using namespace mlpack::bindings;
+using namespace mlpack::bindings::markdown;
+
+void PrintHeaders(const std::string& bindingName,
+                  const std::vector<std::string>& languages)
+{
+  // We just want to print the name of the function and a link, as Markdown.
+  // We have to mark it as having the right language with a div.
+  for (size_t i = 0; i < languages.size(); ++i)
+  {
+    BindingInfo::Language() = languages[i];
+
+    cout << " - [" << GetBindingName(bindingName) << "](#" << languages[i]
+        << "_" << bindingName << "){: .language-link #" << languages[i] << " }"
+        << endl;
+  }
+}
+
+void PrintDocs(const std::string& bindingName,
+               const vector<string>& languages)
+{
+  ProgramDoc& programDoc = BindingInfo::GetProgramDoc(bindingName);
+
+  CLI::RestoreSettings(bindingName);
+
+  // First, for this section, print each of the names.
+  for (size_t i = 0; i < languages.size(); ++i)
+  {
+    BindingInfo::Language() = languages[i];
+
+    cout << "<div class=\"language-title\" id=\"" << languages[i]
+        << "\" markdown=\"1\">" << endl;
+    cout << "## " << GetBindingName(bindingName) << endl;
+    cout << "{: #" << languages[i] << "_" << bindingName << " }" << endl;
+    cout << "</div>" << endl;
+  }
+  cout << endl;
+
+  // Next we want to print the logical name of the binding (that's known by
+  // ProgramInfo).
+  cout << "#### " << programDoc.programName << endl;
+  cout << endl;
+
+  for (size_t i = 0; i < languages.size(); ++i)
+  {
+    BindingInfo::Language() = languages[i];
+
+    cout << "<div class=\"language-decl\" id=\"" << languages[i]
+        << "\" markdown=\"1\">" << endl;
+    cout << ProgramCall(bindingName);
+    cout << "</div>" << endl;
+  }
+  cout << endl;
+
+  cout << programDoc.shortDocumentation << " ";
+  for (size_t i = 0; i < languages.size(); ++i)
+  {
+    cout << "[Detailed documentation](#" << languages[i] << "_"
+      << bindingName << "_detailed-documentation){: .language-detail-link #"
+      << languages[i] << " }";
+  }
+  cout << "." << endl;
+
+  // Next, print the PROGRAM_INFO() documentation for each language.
+  for (size_t i = 0; i < languages.size(); ++i)
+  {
+    BindingInfo::Language() = languages[i];
+
+    // This works with the Kramdown processor.
+    cout << "<div class=\"language-section\" id=\"" << languages[i]
+        << "\" markdown=\"1\">" << endl;
+
+    // We need to print the signature.
+
+    // Now, iterate through each of the input options.
+    cout << endl;
+    cout << "### Input options" << endl;
+    cout << endl;
+
+    cout << "| ***name*** | ***type*** | ***description*** | ***default*** |"
+        << endl;
+    cout << "|------------|------------|-------------------|---------------|"
+        << endl;
+    map<string, ParamData>& parameters = CLI::Parameters();
+    for (map<string, ParamData>::const_iterator it = parameters.begin();
+         it != parameters.end(); ++it)
+    {
+      if (!it->second.input)
+        continue;
+
+      // There are some special options that don't exist in some languages.
+      if (languages[i] != "python" && it->second.name == "copy_all_inputs")
+        continue;
+      if (languages[i] != "cli" &&
+          (it->second.name == "help" || it->second.name == "info" ||
+           it->second.name == "version"))
+        continue;
+
+      // Print name, type, description, default.
+      cout << "| ";
+      cout << ParamString(it->second.name) << " | ";
+      cout << ParamType(it->second) << " | ";
+      cout << it->second.desc; // just a string
+      // Print whether or not it's a "special" language-only parameter.
+      if (it->second.name == "copy_all_inputs" || it->second.name == "help" ||
+          it->second.name == "info" || it->second.name == "version")
+      {
+        cout << "  <span class=\"special\">Only exists in "
+            << PrintLanguage(languages[i]) << " binding.</span>";
+      }
+      cout << " | ";
+      string def = PrintDefault(it->second.name);
+      if (def.size() > 0)
+        cout << "`" << def << "` |";
+      else
+        cout << " |";
+      cout << endl;
+    }
+    cout << endl;
+
+    // Next, iterate through the list of output options.
+    cout << "### Output options" << endl;
+    cout << endl;
+    string outputInfo = PrintOutputOptionInfo();
+    if (outputInfo.size() > 0)
+      cout << outputInfo << endl;
+    cout << endl;
+    cout << "| ***name*** | ***type*** | ***description*** |" << endl;
+    cout << "|------------|------------|-------------------|" << endl;
+    for (map<string, ParamData>::const_iterator it = parameters.begin();
+         it != parameters.end(); ++it)
+    {
+      if (it->second.input)
+        continue;
+
+      // Print name, type, description.
+      cout << "| ";
+      cout << ParamString(it->second.name) << " | ";
+      cout << ParamType(it->second) << " | ";
+      cout << it->second.desc;
+      // Print whether or not it's a "special" language-only parameter.
+      if (it->second.name == "copy_all_inputs" || it->second.name == "help" ||
+          it->second.name == "info" || it->second.name == "version")
+      {
+        cout << "  <span class=\"special\">Only exists in "
+            << PrintLanguage(languages[i]) << " binding.</span>";
+      }
+      cout << " |";
+      cout << endl;
+    }
+    cout << endl;
+
+    cout << "### Detailed documentation" << endl;
+    cout << "{: #" << languages[i] << "_" << bindingName
+        << "_detailed-documentation }" << endl;
+    cout << endl;
+    cout << programDoc.documentation() << endl;
+    cout << endl;
+
+    cout << "### See also" << endl;
+    cout << endl;
+    for (size_t j = 0; j < programDoc.seeAlso.size(); ++j)
+    {
+      cout << " - " << "[";
+      // We need special processing if the user has specified a binding name
+      // starting with @ (i.e., '@kfn' or similar).
+      if (programDoc.seeAlso[j].first[0] == '@')
+        cout << GetBindingName(programDoc.seeAlso[j].first.substr(1));
+      else
+        cout << programDoc.seeAlso[j].first;
+      cout << "](";
+
+      // We need special handling of Doxygen information.
+      if (programDoc.seeAlso[j].second.substr(0, 8) == "@doxygen")
+      {
+        cout << DOXYGEN_PREFIX << programDoc.seeAlso[j].second.substr(9);
+      }
+      else if (programDoc.seeAlso[j].second[0] == '#')
+      {
+        cout << "#" << languages[i] << "_"
+            << programDoc.seeAlso[j].second.substr(1);
+      }
+      else
+      {
+        cout << programDoc.seeAlso[j].second;
+      }
+
+      cout << ")" << endl;
+    }
+    cout << endl;
+
+    cout << "</div>" << endl;
+    cout << endl;
+  }
+
+  CLI::ClearSettings();
+}
diff --git a/src/mlpack/bindings/markdown/print_docs.hpp b/src/mlpack/bindings/markdown/print_docs.hpp
new file mode 100644
index 000000000..abbc88398
--- /dev/null
+++ b/src/mlpack/bindings/markdown/print_docs.hpp
@@ -0,0 +1,33 @@
+/**
+ * @file print_docs.hpp
+ * @author Ryan Curtin
+ *
+ * Functions to generate Markdown for documentation of bindings.
+ */
+#ifndef MLPACK_BINDINGS_MARKDOWN_PRINT_DOCS_HPP
+#define MLPACK_BINDINGS_MARKDOWN_PRINT_DOCS_HPP
+
+#include <mlpack/prereqs.hpp>
+
+/**
+ * Given the current settings of CLI, print the header (which will be the
+ * navigation tab) for the binding types that are registered for these options.
+ *
+ * Output is printed to stdout.
+ */
+void PrintHeaders(const std::string& bindingName,
+                  const std::vector<std::string>& languages);
+
+/**
+ * Given the current settings of CLI, print Markdown documentation for the
+ * binding types that are registered for these options.
+ *
+ * Output is printed to stdout.
+ *
+ * @param bindingName The binding name (${BINDING} from CMake).
+ * @param languages The set of languages to print documentation for.
+ */
+void PrintDocs(const std::string& bindingName,
+               const std::vector<std::string>& languages);
+
+#endif
diff --git a/src/mlpack/bindings/markdown/print_type_doc.hpp b/src/mlpack/bindings/markdown/print_type_doc.hpp
new file mode 100644
index 000000000..108461ed5
--- /dev/null
+++ b/src/mlpack/bindings/markdown/print_type_doc.hpp
@@ -0,0 +1,46 @@
+/**
+ * @file print_type_doc.hpp
+ * @author Ryan Curtin
+ *
+ * Print documentation for a given type, depending on the current language (as
+ * set in BindingInfo).
+ */
+#ifndef MLPACK_BINDINGS_MARKDOWN_PRINT_TYPE_DOC_HPP
+#define MLPACK_BINDINGS_MARKDOWN_PRINT_TYPE_DOC_HPP
+
+#include "binding_info.hpp"
+
+#include <mlpack/bindings/cli/print_type_doc.hpp>
+#include <mlpack/bindings/python/print_type_doc.hpp>
+
+namespace mlpack {
+namespace bindings {
+namespace markdown {
+
+/**
+ * Print the type of a parameter into the output string.  The type printed
+ * depends on the current setting of BindingInfo::Language().
+ */
+template<typename T>
+std::string PrintTypeDoc(const util::ParamData& data)
+{
+  if (BindingInfo::Language() == "cli")
+  {
+    return cli::PrintTypeDoc<typename std::remove_pointer<T>::type>(data);
+  }
+  else if (BindingInfo::Language() == "python")
+  {
+    return python::PrintTypeDoc<typename std::remove_pointer<T>::type>(data);
+  }
+  else
+  {
+    throw std::invalid_argument("PrintTypeDoc(): unknown "
+        "BindingInfo::Language()" + BindingInfo::Language() + "!");
+  }
+}
+
+} // namespace markdown
+} // namespace bindings
+} // namespace mlpack
+
+#endif
diff --git a/src/mlpack/bindings/markdown/program_doc_wrapper.hpp b/src/mlpack/bindings/markdown/program_doc_wrapper.hpp
new file mode 100644
index 000000000..21da3ffc0
--- /dev/null
+++ b/src/mlpack/bindings/markdown/program_doc_wrapper.hpp
@@ -0,0 +1,41 @@
+/**
+ * @file program_doc_wrapper.hpp
+ * @author Ryan Curtin
+ *
+ * A simple wrapper around ProgramDoc that also calls
+ * BindingInfo::RegisterProgramDoc() upon construction.
+ */
+#ifndef MLPACK_BINDINGS_MARKDOWN_PROGRAM_DOC_WRAPPER_HPP
+#define MLPACK_BINDINGS_MARKDOWN_PROGRAM_DOC_WRAPPER_HPP
+
+#include "binding_info.hpp"
+
+namespace mlpack {
+namespace bindings {
+namespace markdown {
+
+class ProgramDocWrapper
+{
+ public:
+  /**
+   * Construct a ProgramDoc object and register it with
+   * BindingInfo::RegisterProgramDoc().
+   */
+  ProgramDocWrapper(const std::string& bindingName,
+                    const std::string& programName,
+                    const std::string& shortDocumentation,
+                    const std::function<std::string()>& documentation,
+                    const std::vector<std::pair<std::string, std::string>>&
+                        seeAlso)
+  {
+    util::ProgramDoc pd(programName, shortDocumentation, documentation,
+        seeAlso);
+    BindingInfo::RegisterProgramDoc(bindingName, pd);
+  }
+};
+
+} // namespace markdown
+} // namespace bindings
+} // namespace mlpack
+
+#endif
diff --git a/src/mlpack/bindings/markdown/res/change_language.js b/src/mlpack/bindings/markdown/res/change_language.js
new file mode 100644
index 000000000..4b520205f
--- /dev/null
+++ b/src/mlpack/bindings/markdown/res/change_language.js
@@ -0,0 +1,102 @@
+/**
+ * A utility function to change the language displayed on the page.  This
+ * function should be called whenever the language is changed from the
+ * drop-down.
+ */
+function changeLanguage()
+{
+  lang = document.getElementById("language-select").value;
+  var links = document.getElementsByClassName("language-link");
+  for (i = 0; i < links.length; ++i)
+  {
+    // With each of the links, we get the inner <a>, but we need the parent
+    // <li>.
+    if (links[i].id == lang || links[i].id == "always")
+      links[i].parentElement.style.display = "list-item";
+    else
+      links[i].parentElement.style.display = "none";
+  }
+
+  var titles = document.getElementsByClassName("language-title");
+  for (i = 0; i < titles.length; ++i)
+  {
+    if (titles[i].id == lang)
+      titles[i].style.display = "inline";
+    else
+      titles[i].style.display = "none";
+  }
+
+  var headers = document.getElementsByClassName("language-header");
+  for (i = 0; i < headers.length; ++i)
+  {
+    if (headers[i].id == lang)
+      headers[i].style.display = "inline";
+    else
+      headers[i].style.display = "none";
+  }
+
+  var decls = document.getElementsByClassName("language-decl");
+  for (i = 0; i < decls.length; ++i)
+  {
+    if (decls[i].id == lang)
+      decls[i].style.display = "inline";
+    else
+      decls[i].style.display = "none";
+  }
+
+  var types = document.getElementsByClassName("language-types");
+  for (i = 0; i < types.length; ++i)
+  {
+    if (types[i].id == lang)
+      types[i].style.display = "inline";
+    else
+      types[i].style.display = "none";
+  }
+
+  var details = document.getElementsByClassName("language-detail-link");
+  for (i = 0; i < details.length; ++i)
+  {
+    if (details[i].id == lang)
+      details[i].style.display = "inline";
+    else
+      details[i].style.display = "none";
+  }
+
+  var sections = document.getElementsByClassName("language-section");
+  for (i = 0; i < sections.length; ++i)
+  {
+    if (sections[i].id == lang)
+      sections[i].style.display = "inline";
+    else
+      sections[i].style.display = "none";
+  }
+}
+
+document.body.onload = function()
+{
+  // Do we need to manually set the language because the user came with an
+  // anchor?
+  if (window.location.hash)
+  {
+    // Try to extract the language.
+    firstUnderscore = window.location.hash.indexOf("_");
+    if (firstUnderscore !== -1)
+    {
+      var lang = window.location.hash.substring(1, firstUnderscore);
+      // Now see if it's in the list of languages.
+      var select = document.getElementById("language-select");
+      for (i = 0; i < select.length; ++i)
+      {
+        var select_lang = select[i].value;
+        // Is the language a match?
+        if (lang === select_lang)
+        {
+          select.value = select_lang;
+          break;
+        }
+      }
+    }
+  }
+
+  changeLanguage();
+}
diff --git a/src/mlpack/bindings/markdown/res/formatting.css b/src/mlpack/bindings/markdown/res/formatting.css
new file mode 100644
index 000000000..487b026e7
--- /dev/null
+++ b/src/mlpack/bindings/markdown/res/formatting.css
@@ -0,0 +1,142 @@
+body
+{
+  background: #000000;
+}
+
+/* Don't display other languages by default. */
+div.language-title, div.language-header, div.language-decl,
+div.language-detail-link, div.language-types, div.language-section
+{
+  display: none;
+}
+
+/* Just display cli documentation. */
+div.language-title#cli, div.language-header#cli, div.language-decl#cli,
+div.language-detail-link#cli, div.language-types#cli, div.language-section#cli
+{
+  display: none;
+}
+
+div#header
+{
+  padding-top: 10px;
+  width: 200px;
+  background: #000000;
+  border-right: 2px solid #333333;
+  height: 100%;
+  position: fixed;
+  z-index: 1;
+  top: 0;
+  left: 0;
+  overflow-y: scroll;
+}
+
+div#header .language-select-div select
+{
+  color: #ffffff;
+  background: transparent;
+  border: none;
+  height: 29px;
+  padding: 5px;
+  width: 190px;
+}
+
+div#header .language-select-div
+{
+  color: #ffffff;
+  border: 2px solid #333333;
+  background: url('../res/menu_bg.png') no-repeat 96% 0;
+  height: 34px;
+  width: 180px;
+  overflow: hidden;
+  margin-bottom: 20px;
+
+  -webkit-border-radius: 5px;
+  -moz-border-radius: 5px;
+  border-radius: 5px;
+}
+
+div#header ul
+{
+  padding-top: 5px;
+  margin-bottom: 5px;
+  color: #bb2000;
+  display: table;
+  text-align: left;
+  padding-left: 0px;
+  margin-left: 15px;
+  font-size: 75%;
+}
+
+div#header ul li
+{
+  line-height: 1.3em;
+}
+
+div#header ul li a
+{
+  color: #eab72c;
+  font-weight: none;
+}
+
+div#header ul li a:hover
+{
+  color: #ffffff;
+  font-weight: none;
+}
+
+div#docs
+{
+  margin-left: 200px;
+}
+
+span.special
+{
+  font-size: 85%;
+  color: #eab72c;
+}
+
+div.category
+{
+  text-align: left;
+}
+
+div.category h5
+{
+  text-align: left;
+  margin-top: 0;
+  margin-bottom: 0;
+  color: #ffffff;
+  font-size: 100%;
+  font-weight: normal;
+  padding: 0;
+}
+
+h2#mlpack-overview, h2.language-types-h2
+{
+  padding-bottom: 0.75em;
+}
+
+div.language-header h1
+{
+  color: #ffffff;
+  text-align: center;
+  border-top: none;
+  border-bottom: 1px solid #eab72c;
+}
+
+div.language-types li code
+{
+  font-weight: bold;
+  color: #eab72c;
+}
+
+div.language-types li
+{
+  padding-bottom: 1em;
+}
+
+div.language-section table td a code:hover
+{
+  color: #bb2000;
+}
diff --git a/src/mlpack/bindings/markdown/res/menu_bg.png b/src/mlpack/bindings/markdown/res/menu_bg.png
new file mode 100644
index 0000000000000000000000000000000000000000..c29580a400267755c0f2a7d1b8f154d329797553
GIT binary patch
literal 395
zcmeAS@N?(olHy`uVBq!ia0vp^Vn8g%!VDyDo&1~%q*&4&eH|GXHuiJ>Nn{1`6_P!I
zd>I(3)EF2VS{N990fib~Fff!FFfhDIU|_JC!N4G1FlSew4N$@$z$e7@|Ns9$rm%z}
zkO5&YUc4A6vSP&wAh~+=Y9P6B<3=C}Q4qtiwFt=JD+%%oW?-oBD6-sRv)}%J<6##S
z{uAOr8O9`UcNcz%T?{vY9QG1VUsv|Wj9gsIB1StofUaP$^K@|xk+_^3kmk^E@KBlv
z3nQb?wiDJ01q~ZqKi}J1EyfTU{PA6^wa?xsyTXMZR(+r2Hbc2uaP~}(9I0R241Yd|
zZn0k0^$2K^YKdz^NlIc#s#S7PDv)9@GBC8%H89jQGzc*?wK6caGBVIL05S}G9<NqF
d(U6;;l9^VCTZ8RB;W<DJ44$rjF6*2UngDHrdC~v?

literal 0
HcmV?d00001

diff --git a/src/mlpack/bindings/python/CMakeLists.txt b/src/mlpack/bindings/python/CMakeLists.txt
index 88ec98a94..80d579edb 100644
--- a/src/mlpack/bindings/python/CMakeLists.txt
+++ b/src/mlpack/bindings/python/CMakeLists.txt
@@ -43,6 +43,8 @@ endif ()
 
 # Nothing in this directory will be compiled into mlpack.
 set(BINDING_SOURCES
+  default_param.hpp
+  default_param_impl.hpp
   generate_pyx.hpp
   generate_pyx.cpp
   get_arma_type.hpp
@@ -62,6 +64,8 @@ set(BINDING_SOURCES
   print_output_processing.hpp
   print_pyx.hpp
   print_pyx.cpp
+  print_type_doc.hpp
+  print_type_doc_impl.hpp
   py_option.hpp
   strip_type.hpp
   mlpack/arma_util.hpp
diff --git a/src/mlpack/bindings/python/default_param.hpp b/src/mlpack/bindings/python/default_param.hpp
new file mode 100644
index 000000000..c1024f3db
--- /dev/null
+++ b/src/mlpack/bindings/python/default_param.hpp
@@ -0,0 +1,95 @@
+/**
+ * @file default_param.hpp
+ * @author Ryan Curtin
+ *
+ * Return the default value of a parameter, depending on its type.
+ *
+ * mlpack is free software; you may redistribute it and/or modify it under the
+ * terms of the 3-clause BSD license.  You should have received a copy of the
+ * 3-clause BSD license along with mlpack.  If not, see
+ * http://www.opensource.org/licenses/BSD-3-Clause for more information.
+ */
+#ifndef MLPACK_BINDINGS_PYTHON_DEFAULT_PARAM_HPP
+#define MLPACK_BINDINGS_PYTHON_DEFAULT_PARAM_HPP
+
+#include <mlpack/prereqs.hpp>
+#include <mlpack/core/util/param_data.hpp>
+#include <mlpack/core/util/is_std_vector.hpp>
+
+namespace mlpack {
+namespace bindings {
+namespace python {
+
+/**
+ * Return the default value of an option.  This is for regular types.
+ */
+template<typename T>
+std::string DefaultParamImpl(
+    const util::ParamData& data,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type* = 0,
+    const typename boost::disable_if<util::IsStdVector<T>>::type* = 0,
+    const typename boost::disable_if<data::HasSerialize<T>>::type* = 0,
+    const typename boost::disable_if<std::is_same<T, std::string>>::type* = 0,
+    const typename boost::disable_if<std::is_same<T,
+        std::tuple<mlpack::data::DatasetInfo, arma::mat>>>::type* = 0);
+
+/**
+ * Return the default value of a vector option.
+ */
+template<typename T>
+std::string DefaultParamImpl(
+    const util::ParamData& data,
+    const typename boost::enable_if<util::IsStdVector<T>>::type* = 0);
+
+/**
+ * Return the default value of a string option.
+ */
+template<typename T>
+std::string DefaultParamImpl(
+    const util::ParamData& data,
+    const typename boost::enable_if<std::is_same<T, std::string>>::type* = 0);
+
+/**
+ * Return the default value of a matrix option, a tuple option, a
+ * serializable option, or a string option (this returns the default filename,
+ * or '' if the default is no file).
+ */
+template<typename T>
+std::string DefaultParamImpl(
+    const util::ParamData& data,
+    const typename boost::enable_if_c<
+        arma::is_arma_type<T>::value ||
+        std::is_same<T, std::tuple<mlpack::data::DatasetInfo,
+                                   arma::mat>>::value>::type* /* junk */ = 0);
+
+/**
+ * Return the default value of a model option (this returns the default
+ * filename, or '' if the default is no file).
+ */
+template<typename T>
+std::string DefaultParamImpl(
+    const util::ParamData& data,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type* = 0,
+    const typename boost::enable_if<data::HasSerialize<T>>::type* = 0);
+
+/**
+ * Return the default value of an option.  This is the function that will be
+ * placed into the CLI functionMap.
+ */
+template<typename T>
+void DefaultParam(const util::ParamData& data,
+                  const void* /* input */,
+                  void* output)
+{
+  std::string* outstr = (std::string*) output;
+  *outstr = DefaultParamImpl<typename std::remove_pointer<T>::type>(data);
+}
+
+} // namespace python
+} // namespace bindings
+} // namespace mlpack
+
+// Include implementation.
+#include "default_param_impl.hpp"
+
+#endif
diff --git a/src/mlpack/bindings/python/default_param_impl.hpp b/src/mlpack/bindings/python/default_param_impl.hpp
new file mode 100644
index 000000000..bae57cce2
--- /dev/null
+++ b/src/mlpack/bindings/python/default_param_impl.hpp
@@ -0,0 +1,147 @@
+/**
+ * @file default_param_impl.hpp
+ * @author Ryan Curtin
+ *
+ * Return the default value of a parameter, depending on its type.
+ *
+ * mlpack is free software; you may redistribute it and/or modify it under the
+ * terms of the 3-clause BSD license.  You should have received a copy of the
+ * 3-clause BSD license along with mlpack.  If not, see
+ * http://www.opensource.org/licenses/BSD-3-Clause for more information.
+ */
+#ifndef MLPACK_BINDINGS_PYTHON_DEFAULT_PARAM_IMPL_HPP
+#define MLPACK_BINDINGS_PYTHON_DEFAULT_PARAM_IMPL_HPP
+
+#include "default_param.hpp"
+
+namespace mlpack {
+namespace bindings {
+namespace python {
+
+/**
+ * Return the default value of an option.
+ */
+template<typename T>
+std::string DefaultParamImpl(
+    const util::ParamData& data,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type* /* junk */,
+    const typename boost::disable_if<util::IsStdVector<T>>::type* /* junk */,
+    const typename boost::disable_if<data::HasSerialize<T>>::type* /* junk */,
+    const typename boost::disable_if<std::is_same<T, std::string>>::type*,
+    const typename boost::disable_if<std::is_same<T,
+        std::tuple<mlpack::data::DatasetInfo, arma::mat>>>::type* /* junk */)
+{
+  std::ostringstream oss;
+  if (std::is_same<T, bool>::value)
+    oss << "False";
+  else
+    oss << boost::any_cast<T>(data.value);
+
+  return oss.str();
+}
+
+/**
+ * Return the default value of a vector option.
+ */
+template<typename T>
+std::string DefaultParamImpl(
+    const util::ParamData& data,
+    const typename boost::enable_if<util::IsStdVector<T>>::type* /* junk */)
+{
+  // Print each element in an array delimited by square brackets.
+  std::ostringstream oss;
+  const T& vector = boost::any_cast<T>(data.value);
+  oss << "[";
+  if (std::is_same<T, std::vector<std::string>>::value)
+  {
+    if (vector.size() > 0)
+    {
+      for (size_t i = 0; i < vector.size() - 1; ++i)
+      {
+        oss << "'" << vector[i] << "', ";
+      }
+
+      oss << "'" << vector[vector.size() - 1] << "'";
+    }
+
+    oss << "]";
+  }
+  else
+  {
+    if (vector.size() > 0)
+    {
+      for (size_t i = 0; i < vector.size() - 1; ++i)
+      {
+        oss << vector[i] << ", ";
+      }
+
+      oss << vector[vector.size() - 1];
+    }
+
+    oss << "]";
+  }
+  return oss.str();
+}
+
+/**
+ * Return the default value of a string option.
+ */
+template<typename T>
+std::string DefaultParamImpl(
+    const util::ParamData& data,
+    const typename boost::enable_if<std::is_same<T, std::string>>::type*)
+{
+  const std::string& s = *boost::any_cast<std::string>(&data.value);
+  return "'" + s + "'";
+}
+
+/**
+ * Return the default value of a matrix option (this returns the default
+ * filename, or '' if the default is no file).
+ */
+template<typename T>
+std::string DefaultParamImpl(
+    const util::ParamData& /* data */,
+    const typename boost::enable_if_c<
+        arma::is_arma_type<T>::value ||
+        std::is_same<T, std::tuple<mlpack::data::DatasetInfo,
+                                   arma::mat>>::value>::type* /* junk */)
+{
+  // Get the filename and return it, or return an empty string.
+  if (std::is_same<T, arma::rowvec>::value ||
+      std::is_same<T, arma::vec>::value)
+  {
+    return "np.empty([0])";
+  }
+  else if (std::is_same<T, arma::Col<size_t>>::value ||
+           std::is_same<T, arma::Row<size_t>>::value)
+  {
+    return "np.empty([0], dtype=np.uint64)";
+  }
+  else if (std::is_same<T, arma::Mat<size_t>>::value)
+  {
+    return "np.empty([0, 0], dtype=np.uint64)";
+  }
+  else
+  {
+    return "np.empty([0, 0])";
+  }
+}
+
+/**
+ * Return the default value of a model option (always "None").
+ */
+template<typename T>
+std::string DefaultParamImpl(
+    const util::ParamData& /* data */,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type* /* junk */,
+    const typename boost::enable_if<data::HasSerialize<T>>::type* /* junk */)
+{
+  return "None";
+}
+
+} // namespace python
+} // namespace bindings
+} // namespace mlpack
+
+#endif
diff --git a/src/mlpack/bindings/python/get_cython_type.hpp b/src/mlpack/bindings/python/get_cython_type.hpp
index e44f4f70b..e44b91eb0 100644
--- a/src/mlpack/bindings/python/get_cython_type.hpp
+++ b/src/mlpack/bindings/python/get_cython_type.hpp
@@ -40,16 +40,6 @@ inline std::string GetCythonType<int>(
   return "int";
 }
 
-template<>
-inline std::string GetCythonType<float>(
-    const util::ParamData& /* d */,
-    const typename boost::disable_if<util::IsStdVector<float>>::type*,
-    const typename boost::disable_if<data::HasSerialize<float>>::type*,
-    const typename boost::disable_if<arma::is_arma_type<float>>::type*)
-{
-  return "float";
-}
-
 template<>
 inline std::string GetCythonType<double>(
     const util::ParamData& /* d */,
diff --git a/src/mlpack/bindings/python/get_printable_type.hpp b/src/mlpack/bindings/python/get_printable_type.hpp
new file mode 100644
index 000000000..51a528b4d
--- /dev/null
+++ b/src/mlpack/bindings/python/get_printable_type.hpp
@@ -0,0 +1,120 @@
+/**
+ * @file get_printable_type.hpp
+ * @author Ryan Curtin
+ *
+ * Template metaprogramming to return the string representation of the Python
+ * type for a given Python binding parameter.
+ *
+ * mlpack is free software; you may redistribute it and/or modify it under the
+ * terms of the 3-clause BSD license.  You should have received a copy of the
+ * 3-clause BSD license along with mlpack.  If not, see
+ * http://www.opensource.org/licenses/BSD-3-Clause for more information.
+ */
+#ifndef MLPACK_BINDINGS_PYTHON_GET_PRINTABLE_TYPE_HPP
+#define MLPACK_BINDINGS_PYTHON_GET_PRINTABLE_TYPE_HPP
+
+#include <mlpack/prereqs.hpp>
+#include <mlpack/core/util/is_std_vector.hpp>
+
+namespace mlpack {
+namespace bindings {
+namespace python {
+
+template<typename T>
+inline std::string GetPrintableType(
+    const util::ParamData& /* d */,
+    const typename boost::disable_if<util::IsStdVector<T>>::type* = 0,
+    const typename boost::disable_if<data::HasSerialize<T>>::type* = 0,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type* = 0,
+    const typename boost::disable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type* = 0);
+
+template<>
+inline std::string GetPrintableType<int>(
+    const util::ParamData& /* d */,
+    const typename boost::disable_if<util::IsStdVector<int>>::type*,
+    const typename boost::disable_if<data::HasSerialize<int>>::type*,
+    const typename boost::disable_if<arma::is_arma_type<int>>::type*,
+    const typename boost::disable_if<std::is_same<int,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type*);
+
+template<>
+inline std::string GetPrintableType<double>(
+    const util::ParamData& /* d */,
+    const typename boost::disable_if<util::IsStdVector<double>>::type*,
+    const typename boost::disable_if<data::HasSerialize<double>>::type*,
+    const typename boost::disable_if<arma::is_arma_type<double>>::type*,
+    const typename boost::disable_if<std::is_same<double,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type*);
+
+template<>
+inline std::string GetPrintableType<std::string>(
+    const util::ParamData& /* d */,
+    const typename boost::disable_if<util::IsStdVector<std::string>>::type*,
+    const typename boost::disable_if<data::HasSerialize<std::string>>::type*,
+    const typename boost::disable_if<arma::is_arma_type<std::string>>::type*,
+    const typename boost::disable_if<std::is_same<std::string,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type*);
+
+template<>
+inline std::string GetPrintableType<size_t>(
+    const util::ParamData& /* d */,
+    const typename boost::disable_if<util::IsStdVector<size_t>>::type*,
+    const typename boost::disable_if<data::HasSerialize<size_t>>::type*,
+    const typename boost::disable_if<arma::is_arma_type<size_t>>::type*,
+    const typename boost::disable_if<std::is_same<size_t,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type*);
+
+template<>
+inline std::string GetPrintableType<bool>(
+    const util::ParamData& /* d */,
+    const typename boost::disable_if<util::IsStdVector<bool>>::type*,
+    const typename boost::disable_if<data::HasSerialize<bool>>::type*,
+    const typename boost::disable_if<arma::is_arma_type<bool>>::type*,
+    const typename boost::disable_if<std::is_same<bool,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type*);
+
+template<typename T>
+inline std::string GetPrintableType(
+    const util::ParamData& d,
+    const typename boost::enable_if<util::IsStdVector<T>>::type* = 0,
+    const typename boost::disable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type* = 0);
+
+template<typename T>
+inline std::string GetPrintableType(
+    const util::ParamData& /* d */,
+    const typename boost::enable_if<arma::is_arma_type<T>>::type* = 0,
+    const typename boost::disable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type* = 0);
+
+template<typename T>
+inline std::string GetPrintableType(
+    const util::ParamData& /* d */,
+    const typename boost::enable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type* = 0);
+
+template<typename T>
+inline std::string GetPrintableType(
+    const util::ParamData& d,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type* = 0,
+    const typename boost::enable_if<data::HasSerialize<T>>::type* = 0,
+    const typename boost::disable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type* = 0);
+
+template<typename T>
+void GetPrintableType(const util::ParamData& d,
+                      const void* /* input */,
+                      void* output)
+{
+  *((std::string*) output) =
+      GetPrintableType<typename std::remove_pointer<T>::type>(d);
+}
+
+} // namespace python
+} // namespace bindings
+} // namespace mlpack
+
+#include "get_printable_type_impl.hpp"
+
+#endif
diff --git a/src/mlpack/bindings/python/get_printable_type_impl.hpp b/src/mlpack/bindings/python/get_printable_type_impl.hpp
new file mode 100644
index 000000000..2031b3435
--- /dev/null
+++ b/src/mlpack/bindings/python/get_printable_type_impl.hpp
@@ -0,0 +1,151 @@
+/**
+ * @file get_printable_type_impl.hpp
+ * @author Ryan Curtin
+ *
+ * Template metaprogramming to return the string representation of the Python
+ * type for a given Python binding parameter.
+ *
+ * mlpack is free software; you may redistribute it and/or modify it under the
+ * terms of the 3-clause BSD license.  You should have received a copy of the
+ * 3-clause BSD license along with mlpack.  If not, see
+ * http://www.opensource.org/licenses/BSD-3-Clause for more information.
+ */
+#ifndef MLPACK_BINDINGS_PYTHON_GET_PRINTABLE_TYPE_IMPL_HPP
+#define MLPACK_BINDINGS_PYTHON_GET_PRINTABLE_TYPE_IMPL_HPP
+
+#include "get_printable_type.hpp"
+
+namespace mlpack {
+namespace bindings {
+namespace python {
+
+template<typename T>
+inline std::string GetPrintableType(
+    const util::ParamData& /* d */,
+    const typename boost::disable_if<util::IsStdVector<T>>::type*,
+    const typename boost::disable_if<data::HasSerialize<T>>::type*,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type*,
+    const typename boost::disable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type*)
+{
+  return "unknown";
+}
+
+template<>
+inline std::string GetPrintableType<int>(
+    const util::ParamData& /* d */,
+    const typename boost::disable_if<util::IsStdVector<int>>::type*,
+    const typename boost::disable_if<data::HasSerialize<int>>::type*,
+    const typename boost::disable_if<arma::is_arma_type<int>>::type*,
+    const typename boost::disable_if<std::is_same<int,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type*)
+{
+  return "int";
+}
+
+template<>
+inline std::string GetPrintableType<double>(
+    const util::ParamData& /* d */,
+    const typename boost::disable_if<util::IsStdVector<double>>::type*,
+    const typename boost::disable_if<data::HasSerialize<double>>::type*,
+    const typename boost::disable_if<arma::is_arma_type<double>>::type*,
+    const typename boost::disable_if<std::is_same<double,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type*)
+{
+  return "double";
+}
+
+template<>
+inline std::string GetPrintableType<std::string>(
+    const util::ParamData& /* d */,
+    const typename boost::disable_if<util::IsStdVector<std::string>>::type*,
+    const typename boost::disable_if<data::HasSerialize<std::string>>::type*,
+    const typename boost::disable_if<arma::is_arma_type<std::string>>::type*,
+    const typename boost::disable_if<std::is_same<std::string,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type*)
+{
+  return "string";
+}
+
+template<>
+inline std::string GetPrintableType<size_t>(
+    const util::ParamData& /* d */,
+    const typename boost::disable_if<util::IsStdVector<size_t>>::type*,
+    const typename boost::disable_if<data::HasSerialize<size_t>>::type*,
+    const typename boost::disable_if<arma::is_arma_type<size_t>>::type*,
+    const typename boost::disable_if<std::is_same<size_t,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type*)
+{
+  return "size_t";
+}
+
+template<>
+inline std::string GetPrintableType<bool>(
+    const util::ParamData& /* d */,
+    const typename boost::disable_if<util::IsStdVector<bool>>::type*,
+    const typename boost::disable_if<data::HasSerialize<bool>>::type*,
+    const typename boost::disable_if<arma::is_arma_type<bool>>::type*,
+    const typename boost::disable_if<std::is_same<bool,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type*)
+{
+  return "bool";
+}
+
+template<typename T>
+inline std::string GetPrintableType(
+    const util::ParamData& d,
+    const typename boost::enable_if<util::IsStdVector<T>>::type*,
+    const typename boost::disable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type*)
+{
+  return "list of " + GetPrintableType<typename T::value_type>(d) + "s";
+}
+
+template<typename T>
+inline std::string GetPrintableType(
+    const util::ParamData& /* d */,
+    const typename boost::enable_if<arma::is_arma_type<T>>::type*,
+    const typename boost::disable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type*)
+{
+  std::string type = "matrix";
+  if (std::is_same<typename T::elem_type, double>::value)
+  {
+    if (T::is_row || T::is_col)
+      type = "vector";
+  }
+  else if (std::is_same<typename T::elem_type, size_t>::value)
+  {
+    type = "int matrix";
+    if (T::is_row || T::is_col)
+      type = "int vector";
+  }
+
+  return type;
+}
+
+template<typename T>
+inline std::string GetPrintableType(
+    const util::ParamData& /* d */,
+    const typename boost::enable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type*)
+{
+  return "categorical matrix";
+}
+
+template<typename T>
+inline std::string GetPrintableType(
+    const util::ParamData& d,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type*,
+    const typename boost::enable_if<data::HasSerialize<T>>::type*,
+    const typename boost::disable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type*)
+{
+  return d.cppType + "Type";
+}
+
+} // namespace python
+} // namespace bindings
+} // namespace mlpack
+
+#endif
diff --git a/src/mlpack/bindings/python/print_doc.hpp b/src/mlpack/bindings/python/print_doc.hpp
index 7a5a27781..b8962ccaf 100644
--- a/src/mlpack/bindings/python/print_doc.hpp
+++ b/src/mlpack/bindings/python/print_doc.hpp
@@ -14,7 +14,7 @@
 
 #include <mlpack/prereqs.hpp>
 #include <mlpack/core/util/hyphenate_string.hpp>
-#include "get_python_type.hpp"
+#include "get_printable_type.hpp"
 
 namespace mlpack {
 namespace bindings {
@@ -44,24 +44,20 @@ void PrintDoc(const util::ParamData& d,
     oss << d.name << "_ (";
   else
     oss << d.name << " (";
-  oss << GetPythonType<typename std::remove_pointer<T>::type>(d) << "): "
+  oss << GetPrintableType<typename std::remove_pointer<T>::type>(d) << "): "
       << d.desc;
 
   // Print a default, if possible.
   if (!d.required)
   {
-    if (d.cppType == "std::string")
+    // Call the correct overload to get the default value directly.
+    if (d.cppType == "std::string" || d.cppType == "double" ||
+        d.cppType == "int" || d.cppType == "std::vector<int>" ||
+        d.cppType == "std::vector<std::string>" ||
+        d.cppType == "std::vector<double>")
     {
-      oss << "  Default value '" << boost::any_cast<std::string>(d.value)
-          << "'.";
-    }
-    else if (d.cppType == "double")
-    {
-      oss << "  Default value " << boost::any_cast<double>(d.value) << ".";
-    }
-    else if (d.cppType == "int")
-    {
-      oss << "  Default value " << boost::any_cast<int>(d.value) << ".";
+      std::string defaultValue = DefaultParamImpl<T>(d);
+      oss << "  Default value " << defaultValue << ".";
     }
   }
 
diff --git a/src/mlpack/bindings/python/print_doc_functions.hpp b/src/mlpack/bindings/python/print_doc_functions.hpp
index a9207c570..51e85b78d 100644
--- a/src/mlpack/bindings/python/print_doc_functions.hpp
+++ b/src/mlpack/bindings/python/print_doc_functions.hpp
@@ -19,6 +19,21 @@ namespace mlpack {
 namespace bindings {
 namespace python {
 
+/**
+ * Given the name of a binding, print its Python name.
+ */
+inline std::string GetBindingName(const std::string& bindingName);
+
+/**
+ * Print any import information for the Python binding.
+ */
+inline std::string PrintImport(const std::string& bindingName);
+
+/**
+ * Print any special information about output options.
+ */
+inline std::string PrintOutputOptionInfo();
+
 /**
  * Given a parameter type, print the corresponding value.
  */
@@ -29,6 +44,11 @@ inline std::string PrintValue(const T& value, bool quotes);
 template<>
 inline std::string PrintValue(const bool& value, bool quotes);
 
+/**
+ * Given a parameter name, print its corresponding default value.
+ */
+inline std::string PrintDefault(const std::string& paramName);
+
 // Recursion base case.
 inline std::string PrintInputOptions();
 
@@ -57,6 +77,12 @@ std::string PrintOutputOptions(const std::string& paramName,
 template<typename... Args>
 std::string ProgramCall(const std::string& programName, Args... args);
 
+/**
+ * Given the name of a binding, print a program call assuming that all options
+ * are specified.
+ */
+inline std::string ProgramCall(const std::string& programName);
+
 /**
  * Given the name of a model, print it.  Here we do not need to modify anything.
  */
diff --git a/src/mlpack/bindings/python/print_doc_functions_impl.hpp b/src/mlpack/bindings/python/print_doc_functions_impl.hpp
index c27e88609..0de6a5312 100644
--- a/src/mlpack/bindings/python/print_doc_functions_impl.hpp
+++ b/src/mlpack/bindings/python/print_doc_functions_impl.hpp
@@ -19,6 +19,32 @@ namespace mlpack {
 namespace bindings {
 namespace python {
 
+/**
+ * Given the name of a binding, print its Python name.
+ */
+inline std::string GetBindingName(const std::string& bindingName)
+{
+  // No modification is needed to the name---we just use it as-is.
+  return bindingName + "()";
+}
+
+/**
+ * Print any import information for the Python binding.
+ */
+inline std::string PrintImport(const std::string& bindingName)
+{
+  return "from mlpack import " + bindingName;
+}
+
+/**
+ * Print any special information about output options.
+ */
+inline std::string PrintOutputOptionInfo()
+{
+  return "Results are returned in a Python dictionary.  The keys of the "
+      "dictionary are the names of the output parameters.";
+}
+
 /**
  * Given a parameter type, print the corresponding value.
  */
@@ -48,6 +74,23 @@ inline std::string PrintValue(const bool& value, bool quotes)
     return "False";
 }
 
+/**
+ * Given a parameter name, print its corresponding default value.
+ */
+inline std::string PrintDefault(const std::string& paramName)
+{
+  if (CLI::Parameters().count(paramName) == 0)
+    throw std::invalid_argument("unknown parameter " + paramName + "!");
+
+  const util::ParamData& d = CLI::Parameters()[paramName];
+
+  std::string defaultValue;
+  CLI::GetSingleton().functionMap[d.tname]["DefaultParam"](d, NULL,
+      (void*) &defaultValue);
+
+  return defaultValue;
+}
+
 // Recursion base case.
 std::string PrintInputOptions() { return ""; }
 
@@ -136,7 +179,8 @@ std::string PrintOutputOptions(const std::string& paramName,
 
 /**
  * Given a name of a binding and a variable number of arguments (and their
- * contents), print the corresponding function call.
+ * contents), print the corresponding function call.  The given programName
+ * should not be the output of GetBindingName().
  */
 template<typename... Args>
 std::string ProgramCall(const std::string& programName, Args... args)
@@ -166,6 +210,76 @@ std::string ProgramCall(const std::string& programName, Args... args)
     return util::HyphenateString(call, 2) + "\n" + oss.str();
 }
 
+/**
+ * Given the name of a binding, print a program call assuming that all options
+ * are specified.  The programName should not be the output of GetBindingName().
+ */
+inline std::string ProgramCall(const std::string& programName)
+{
+  std::ostringstream oss;
+  oss << ">>> ";
+
+  // Determine if we have any output options.
+  const std::map<std::string, util::ParamData>& parameters = CLI::Parameters();
+  bool hasOutput = false;
+  for (auto it = parameters.begin(); it != parameters.end(); ++it)
+  {
+    if (!it->second.input)
+    {
+      hasOutput = true;
+      break;
+    }
+  }
+
+  if (hasOutput)
+    oss << "d = ";
+
+  oss << programName << "(";
+
+  // Now iterate over every input option.
+  bool first = true;
+  for (auto it = parameters.begin(); it != parameters.end(); ++it)
+  {
+    if (!it->second.input || it->second.persistent)
+      continue;
+
+    if (!first)
+      oss << ", ";
+    else
+      first = false;
+
+    // Print the input option.
+    if (it->second.name != "lambda") // Don't print Python keywords.
+      oss << it->second.name << "=";
+    else
+      oss << it->second.name << "_=";
+
+    std::string value;
+    CLI::GetSingleton().functionMap[it->second.tname]["DefaultParam"](
+        it->second, NULL, (void*) &value);
+    oss << value;
+  }
+  oss << ")";
+
+  std::string result = util::HyphenateString(oss.str(), 8);
+
+  oss.str("");
+  oss << result;
+
+  // Now print output lines.
+  for (auto it = parameters.begin(); it != parameters.end(); ++it)
+  {
+    if (it->second.input)
+      continue;
+
+    // Print a new line for the output option.
+    oss << std::endl << ">>> " << it->second.name << " = d['"
+        << it->second.name << "']";
+  }
+
+  return oss.str();
+}
+
 /**
  * Given the name of a model, print it.  Here we do not need to modify anything.
  */
@@ -183,14 +297,6 @@ inline std::string PrintDataset(const std::string& datasetName)
   return "'" + datasetName + "'";
 }
 
-/**
- * Given the name of a binding, print its invocation.
- */
-inline std::string ProgramCall(const std::string& programName)
-{
-  return ">>> " + programName + "(";
-}
-
 /**
  * Print any closing call to a program.  For a Python binding this is a closing
  * brace.
diff --git a/src/mlpack/bindings/python/print_input_processing.hpp b/src/mlpack/bindings/python/print_input_processing.hpp
index e271906cb..04f318a7c 100644
--- a/src/mlpack/bindings/python/print_input_processing.hpp
+++ b/src/mlpack/bindings/python/print_input_processing.hpp
@@ -17,7 +17,6 @@
 #include "get_numpy_type.hpp"
 #include "get_numpy_type_char.hpp"
 #include "get_cython_type.hpp"
-#include "get_python_type.hpp"
 #include "strip_type.hpp"
 
 namespace mlpack {
diff --git a/src/mlpack/bindings/python/print_type_doc.hpp b/src/mlpack/bindings/python/print_type_doc.hpp
new file mode 100644
index 000000000..9a5d6dc71
--- /dev/null
+++ b/src/mlpack/bindings/python/print_type_doc.hpp
@@ -0,0 +1,81 @@
+/**
+ * @file print_type_doc.hpp
+ * @author Ryan Curtin
+ *
+ * Print documentation for a given type, detailing what the type actually is to
+ * the user.
+ */
+#ifndef MLPACK_BINDINGS_PYTHON_PRINT_TYPE_DOC_HPP
+#define MLPACK_BINDINGS_PYTHON_PRINT_TYPE_DOC_HPP
+
+#include <mlpack/core/util/is_std_vector.hpp>
+
+namespace mlpack {
+namespace bindings {
+namespace python {
+
+/**
+ * Return a string representing the command-line type of an option.
+ */
+template<typename T>
+std::string PrintTypeDoc(
+    const util::ParamData& data,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type* = 0,
+    const typename boost::disable_if<util::IsStdVector<T>>::type* = 0,
+    const typename boost::disable_if<data::HasSerialize<T>>::type* = 0,
+    const typename boost::disable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type* = 0);
+
+/**
+ * Return a string representing the command-line type of a vector.
+ */
+template<typename T>
+std::string PrintTypeDoc(
+    const util::ParamData& data,
+    const typename std::enable_if<util::IsStdVector<T>::value>::type* = 0);
+
+/**
+ * Return a string representing the command-line type of a matrix option.
+ */
+template<typename T>
+std::string PrintTypeDoc(
+    const util::ParamData& data,
+    const typename std::enable_if<arma::is_arma_type<T>::value>::type* = 0);
+
+/**
+ * Return a string representing the command-line type of a matrix tuple option.
+ */
+template<typename T>
+std::string PrintTypeDoc(
+    const util::ParamData& data,
+    const typename std::enable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>::value>::type* = 0);
+
+/**
+ * Return a string representing the command-line type of a model.
+ */
+template<typename T>
+std::string PrintTypeDoc(
+    const util::ParamData& data,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type* = 0,
+    const typename boost::enable_if<data::HasSerialize<T>>::type* = 0);
+
+/**
+ * Print the command-line type of an option into a string.
+ */
+template<typename T>
+void PrintTypeDoc(const util::ParamData& data,
+                  const void* /* input */,
+                  void* output)
+{
+  *((std::string*) output) =
+      PrintTypeDoc<typename std::remove_pointer<T>::type>(data);
+}
+
+} // namespace python
+} // namespace bindings
+} // namespace mlpack
+
+#include "print_type_doc_impl.hpp"
+
+#endif
diff --git a/src/mlpack/bindings/python/print_type_doc_impl.hpp b/src/mlpack/bindings/python/print_type_doc_impl.hpp
new file mode 100644
index 000000000..8d6ae3971
--- /dev/null
+++ b/src/mlpack/bindings/python/print_type_doc_impl.hpp
@@ -0,0 +1,162 @@
+/**
+ * @file print_type_doc_impl.hpp
+ * @author Ryan Curtin
+ *
+ * Print documentation for a given type.
+ */
+#ifndef MLPACK_BINDINGS_PYTHON_PRINT_TYPE_DOC_IMPL_HPP
+#define MLPACK_BINDINGS_PYTHON_PRINT_TYPE_DOC_IMPL_HPP
+
+#include "print_type_doc.hpp"
+
+namespace mlpack {
+namespace bindings {
+namespace python {
+
+/**
+ * Return a string representing the command-line type of an option.
+ */
+template<typename T>
+std::string PrintTypeDoc(
+    const util::ParamData& data,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type*,
+    const typename boost::disable_if<util::IsStdVector<T>>::type*,
+    const typename boost::disable_if<data::HasSerialize<T>>::type*,
+    const typename boost::disable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>>::type*)
+{
+  // A flag type.
+  if (std::is_same<T, bool>::value)
+  {
+    return "A boolean flag option (True or False).";
+  }
+  // An integer.
+  else if (std::is_same<T, int>::value)
+  {
+    return "An integer (i.e., \"1\").";
+  }
+  // A floating point value.
+  else if (std::is_same<T, double>::value)
+  {
+    return "A floating-point number (i.e., \"0.5\").";
+  }
+  // A string.
+  else if (std::is_same<T, std::string>::value)
+  {
+    return "A character string (i.e., \"hello\").";
+  }
+  // Not sure what it is...
+  else
+  {
+    throw std::invalid_argument("unknown parameter type " + data.cppType);
+  }
+}
+
+/**
+ * Return a string representing the command-line type of a vector.
+ */
+template<typename T>
+std::string PrintTypeDoc(
+    const util::ParamData& data,
+    const typename std::enable_if<util::IsStdVector<T>::value>::type*)
+{
+  if (std::is_same<T, std::vector<int>>::value)
+  {
+    return "A list of integers; i.e., [0, 1, 2].";
+  }
+  else if (std::is_same<T, std::vector<std::string>>::value)
+  {
+    return "A list of strings; i.e., [\"hello\", \"goodbye\"].";
+  }
+  else
+  {
+    throw std::invalid_argument("unknown vector type " + data.cppType);
+  }
+}
+
+/**
+ * Return a string representing the command-line type of a matrix option.
+ */
+template<typename T>
+std::string PrintTypeDoc(
+    const util::ParamData& data,
+    const typename std::enable_if<arma::is_arma_type<T>::value>::type*)
+{
+  if (std::is_same<typename T::elem_type, double>::value)
+  {
+    if (T::is_col || T::is_row)
+    {
+      return "A 1-d arraylike containing data.  This can be a 2-d matrix where "
+          "one dimension has size 1, or it can also be a list, a numpy 1-d "
+          "ndarray, or a 1-d pandas DataFrame.  If the dtype is not already "
+          "float64, it will be converted.";
+    }
+    else
+    {
+      return "A 2-d arraylike containing data.  This can be a list of lists, a "
+          "numpy ndarray, or a pandas DataFrame.  If the dtype is not already "
+          "float64, it will be converted.";
+    }
+  }
+  else if (std::is_same<typename T::elem_type, size_t>::value)
+  {
+    if (T::is_col || T::is_row)
+    {
+      return "A 1-d arraylike containing data with a uint64 dtype.  This can be"
+          " a 2-d matrix where one dimension has size 1, or it can also be a "
+          "list, a numpy 1-d ndarray, or a 1-d pandas DataFrame.  If the dtype "
+          "is not already uint64, it will be converted.";
+    }
+    else
+    {
+      return "A 2-d arraylike containing data with a uint64 dtype.  This can "
+          "be a list of lists, a numpy ndarray, or a pandas DataFrame.  If the "
+          "dtype is not already uint64, it will be converted.";
+    }
+  }
+  else
+  {
+    throw std::invalid_argument("unknown matrix type " + data.cppType);
+  }
+}
+
+/**
+ * Return a string representing the command-line type of a matrix tuple option.
+ */
+template<typename T>
+std::string PrintTypeDoc(
+    const util::ParamData& /* data */,
+    const typename std::enable_if<std::is_same<T,
+        std::tuple<data::DatasetInfo, arma::mat>>::value>::type*)
+{
+  return "A 2-d arraylike containing data.  Like the regular 2-d matrices, this"
+      " can be a list of lists, a numpy ndarray, or a pandas DataFrame. "
+      "However, this type can also accept a pandas DataFrame that has columns "
+      "of type 'CategoricalDtype'.  These categorical values will be converted "
+      "to numeric indices before being passed to mlpack, and then inside mlpack"
+      " they will be properly treated as categorical variables, so there is no "
+      "need to do one-hot encoding for this matrix type.  If the dtype of the "
+      "given matrix is not already float64, it will be converted.";
+}
+
+/**
+ * Return a string representing the command-line type of a model.
+ */
+template<typename T>
+std::string PrintTypeDoc(
+    const util::ParamData& /* data */,
+    const typename boost::disable_if<arma::is_arma_type<T>>::type*,
+    const typename boost::enable_if<data::HasSerialize<T>>::type*)
+{
+  return "An mlpack model pointer.  This type can be pickled to or from disk, "
+      "and internally holds a pointer to C++ memory containing the mlpack "
+      "model.  Note that this means that the mlpack model itself cannot be "
+      "easily inspected in Python; however, the pickled model can be loaded "
+      "in C++ and inspected there.";
+}
+
+} // namespace python
+} // namespace bindings
+} // namespace mlpack
+
+#endif
diff --git a/src/mlpack/bindings/python/py_option.hpp b/src/mlpack/bindings/python/py_option.hpp
index 98bba2dd5..e175d282b 100644
--- a/src/mlpack/bindings/python/py_option.hpp
+++ b/src/mlpack/bindings/python/py_option.hpp
@@ -13,6 +13,7 @@
 #define MLPACK_BINDINGS_PYTHON_PY_OPTION_HPP
 
 #include <mlpack/core/util/param_data.hpp>
+#include "default_param.hpp"
 #include "get_param.hpp"
 #include "get_printable_param.hpp"
 #include "print_class_defn.hpp"
@@ -85,6 +86,9 @@ class PyOption
     CLI::GetSingleton().functionMap[data.tname]["GetPrintableParam"] =
         &GetPrintableParam<T>;
 
+    CLI::GetSingleton().functionMap[data.tname]["DefaultParam"] =
+        &DefaultParam<T>;
+
     // These are used by the pyx generator.
     CLI::GetSingleton().functionMap[data.tname]["PrintClassDefn"] =
         &PrintClassDefn<T>;
diff --git a/src/mlpack/bindings/python/tests/test_python_binding_main.cpp b/src/mlpack/bindings/python/tests/test_python_binding_main.cpp
index cf3c1055d..93daaddc4 100644
--- a/src/mlpack/bindings/python/tests/test_python_binding_main.cpp
+++ b/src/mlpack/bindings/python/tests/test_python_binding_main.cpp
@@ -19,6 +19,7 @@ using namespace mlpack;
 using namespace mlpack::kernel;
 
 PROGRAM_INFO("Python binding test",
+    "A simple program to test Python binding functionality.",
     "A simple program to test Python binding functionality.  You can build "
     "mlpack with the BUILD_TESTS option set to off, and this binding will "
     "no longer be built.");
diff --git a/src/mlpack/core/util/cli.cpp b/src/mlpack/core/util/cli.cpp
index f2bf6cec2..1a83080bd 100644
--- a/src/mlpack/core/util/cli.cpp
+++ b/src/mlpack/core/util/cli.cpp
@@ -27,7 +27,8 @@ using namespace mlpack;
 using namespace mlpack::util;
 
 // Fake ProgramDoc in case none is supplied.
-static ProgramDoc emptyProgramDoc = ProgramDoc("", []() { return ""; });
+static ProgramDoc emptyProgramDoc = ProgramDoc("", "", []() { return ""; },
+    {});
 
 /* Constructors, Destructors, Copy */
 /* Make the constructor private, to preclude unauthorized instances */
diff --git a/src/mlpack/core/util/mlpack_main.hpp b/src/mlpack/core/util/mlpack_main.hpp
index 4c915c0c0..8003f38ac 100644
--- a/src/mlpack/core/util/mlpack_main.hpp
+++ b/src/mlpack/core/util/mlpack_main.hpp
@@ -21,6 +21,7 @@
 #define BINDING_TYPE_CLI 0
 #define BINDING_TYPE_TEST 1
 #define BINDING_TYPE_PYX 2
+#define BINDING_TYPE_MARKDOWN 128
 #define BINDING_TYPE_UNKNOWN -1
 
 #ifndef BINDING_TYPE
@@ -99,9 +100,10 @@ using Option = mlpack::bindings::tests::TestOption<T>;
 #include <mlpack/core/util/param.hpp>
 
 #undef PROGRAM_INFO
-#define PROGRAM_INFO(NAME, DESC) static mlpack::util::ProgramDoc \
-    cli_programdoc_dummy_object = mlpack::util::ProgramDoc(NAME, \
-        []() { return DESC; });
+#define PROGRAM_INFO(NAME, SHORT_DESC, DESC, ...) \
+    static mlpack::util::ProgramDoc \
+    cli_programdoc_dummy_object = mlpack::util::ProgramDoc(NAME, SHORT_DESC, \
+    []() { return DESC; }, { __VA_ARGS__ })
 
 #elif(BINDING_TYPE == BINDING_TYPE_PYX) // This is a Python binding.
 
@@ -128,9 +130,10 @@ static const std::string testName = "";
 #include <mlpack/core/util/param.hpp>
 
 #undef PROGRAM_INFO
-#define PROGRAM_INFO(NAME, DESC) static mlpack::util::ProgramDoc \
-    cli_programdoc_dummy_object = mlpack::util::ProgramDoc(NAME, \
-        []() { return DESC; }); \
+#define PROGRAM_INFO(NAME, SHORT_DESC, DESC, ...) \
+    static mlpack::util::ProgramDoc \
+    cli_programdoc_dummy_object = mlpack::util::ProgramDoc(NAME, SHORT_DESC, \
+    []() { return DESC; }, { __VA_ARGS__ }); \
     namespace mlpack { \
     namespace bindings { \
     namespace python { \
@@ -148,6 +151,59 @@ PARAM_FLAG("copy_all_inputs", "If specified, all input parameters will be deep"
 
 // Nothing else needs to be defined---the binding will use mlpackMain() as-is.
 
+#elif BINDING_TYPE == BINDING_TYPE_MARKDOWN
+
+// It doesn't really matter whether this is true or false...
+#define BINDING_MATRIX_TRANSPOSED true
+
+// We use BINDING_NAME in PROGRAM_INFO() so it needs to be defined.
+#ifndef BINDING_NAME
+  #error "BINDING_NAME must be defined when BINDING_TYPE is Markdown!"
+#endif
+
+#include <mlpack/bindings/markdown/md_option.hpp>
+#include <mlpack/bindings/markdown/print_doc_functions.hpp>
+
+#define PRINT_PARAM_STRING mlpack::bindings::markdown::ParamString
+#define PRINT_PARAM_VALUE mlpack::bindings::markdown::PrintValue
+#define PRINT_DATASET mlpack::bindings::markdown::PrintDataset
+#define PRINT_MODEL mlpack::bindings::markdown::PrintModel
+#define PRINT_CALL mlpack::bindings::markdown::ProgramCall
+#define BINDING_IGNORE_CHECK mlpack::bindings::markdown::IgnoreCheck
+
+namespace mlpack {
+namespace util {
+
+template<typename T>
+using Option = mlpack::bindings::markdown::MDOption<T>;
+
+}
+}
+
+#include <mlpack/core/util/param.hpp>
+#include <mlpack/bindings/markdown/program_doc_wrapper.hpp>
+
+#undef PROGRAM_INFO
+#define PROGRAM_INFO(NAME, SHORT_DESC, DESC, ...) static \
+    mlpack::bindings::markdown::ProgramDocWrapper \
+    cli_programdoc_dummy_object = \
+    mlpack::bindings::markdown::ProgramDocWrapper(BINDING_NAME, NAME, \
+    SHORT_DESC, []() { return DESC; }, { __VA_ARGS__ }); \
+
+PARAM_FLAG("verbose", "Display informational messages and the full list of "
+    "parameters and timers at the end of execution.", "v");
+
+// CLI-specific parameters.
+PARAM_FLAG("help", "Default help info.", "h");
+PARAM_STRING_IN("info", "Print help on a specific option.", "", "");
+PARAM_FLAG("version", "Display the version of mlpack.", "V");
+
+// Python-specific parameters.
+PARAM_FLAG("copy_all_inputs", "If specified, all input parameters will be deep"
+    " copied before the method is run.  This is useful for debugging problems "
+    "where the input parameters are being modified by the algorithm, but can "
+    "slow down the code.", "");
+
 #else
 
 #error "Unknown binding type!  Be sure BINDING_TYPE is defined if you are " \
diff --git a/src/mlpack/core/util/param.hpp b/src/mlpack/core/util/param.hpp
index b812887fb..de50f5b00 100644
--- a/src/mlpack/core/util/param.hpp
+++ b/src/mlpack/core/util/param.hpp
@@ -29,6 +29,21 @@ using DatasetInfo = DatasetMapper<IncrementPolicy, std::string>;
 } // namespace data
 } // namespace mlpack
 
+/**
+ * Provide a link for a binding's "see also" documentation section, which is
+ * primarily (but not necessarily exclusively) used by the Markdown bindings
+ * This link can be specified by calling SEE_ALSO("description", "link"), where
+ * "description" is the description of the link and "link" may be one of the
+ * following:
+ *
+ * - A direct URL, starting with http:// or https://.
+ * - A page anchor for documentation, referencing another binding by its CMake
+ *      binding name, i.e. "#knn".
+ * - A link to a Doxygen page, using the mangled Doxygen name after a
+ *      '@doxygen/', i.e., "@doxygen/mlpack1_1_adaboost1_1_AdaBoost".
+ */
+#define SEE_ALSO(DESCRIPTION, LINK) {DESCRIPTION, LINK}
+
 /**
  * Document an executable.  Only one instance of this macro should be
  * present in your program!  Therefore, use it in the main.cpp
@@ -41,14 +56,22 @@ using DatasetInfo = DatasetMapper<IncrementPolicy, std::string>;
  * PARAM_DOUBLE_OUT_REQ(), PARAM_VECTOR_OUT_REQ(), PARAM_STRING_OUT_REQ().
  *
  * @param NAME Short string representing the name of the program.
+ * @param SHORT_DESC Short two-sentence description of the program; it should
+ *     describe what the program implements and does, and a quick overview of
+ *     how it can be used and what it should be used for.
  * @param DESC Long string describing what the program does and possibly a
  *     simple usage example.  Newlines should not be used here; this is taken
  *     care of by CLI (however, you can explicitly specify newlines to denote
- *     new paragraphs).
+ *     new paragraphs).  You can also use printing macros like
+ *     PRINT_PARAM_STRING(), PRINT_DATASET(), and others.
+ * @param SEE_ALSOS A set of SEE_ALSO() macros that are used for generating
+ *     documentation.  See the SEE_ALSO() macro.  This is a varargs argument, so
+ *     you can add as many SEE_ALSO()s as you like.
  */
-#define PROGRAM_INFO(NAME, DESC) static mlpack::util::ProgramDoc \
-    cli_programdoc_dummy_object = mlpack::util::ProgramDoc(NAME, \
-    []() { return DESC; })
+#define PROGRAM_INFO(NAME, SHORT_DESC, DESC, ...) \
+    static mlpack::util::ProgramDoc \
+    cli_programdoc_dummy_object = mlpack::util::ProgramDoc(NAME, SHORT_DESC, \
+    []() { return DESC; }, { __VA_ARGS__ } )
 
 /**
  * Define a flag parameter.
diff --git a/src/mlpack/core/util/program_doc.cpp b/src/mlpack/core/util/program_doc.cpp
index 814573b1c..28d7ee8b8 100644
--- a/src/mlpack/core/util/program_doc.cpp
+++ b/src/mlpack/core/util/program_doc.cpp
@@ -23,17 +23,33 @@ using namespace std;
  * Construct a ProgramDoc object.  When constructed, it will register itself
  * with CLI.  A fatal error will be thrown if more than one is constructed.
  *
- * @param programName Short string representing the name of the program.
- * @param documentation Long string containing documentation on how to use the
- *    program and what it is.  No newline characters are necessary; this is
- *    taken care of by CLI later.
  * @param defaultModule Name of the default module.
+ * @param shortDocumentation A short two-sentence description of the program,
+ *     what it does, and what it is useful for.
+ * @param documentation Long string containing documentation on how to use the
+ *     program and what it is.  No newline characters are necessary; this is
+ *     taken care of by CLI later.
+ * @param seeAlso A set of pairs of strings with useful "see also"
+ *     information; each pair is <description, url>.
  */
-ProgramDoc::ProgramDoc(const std::string& programName,
-                       const std::function<std::string()>& documentation) :
+ProgramDoc::ProgramDoc(
+    const std::string& programName,
+    const std::string& shortDocumentation,
+    const std::function<std::string()>& documentation,
+    const std::vector<std::pair<std::string, std::string>>& seeAlso) :
     programName(programName),
-    documentation(documentation)
+    shortDocumentation(shortDocumentation),
+    documentation(documentation),
+    seeAlso(seeAlso)
 {
   // Register this with CLI.
   CLI::RegisterProgramDoc(this);
 }
+
+/**
+ * Construct an empty ProgramDoc object.
+ */
+ProgramDoc::ProgramDoc()
+{
+  CLI::RegisterProgramDoc(this);
+}
diff --git a/src/mlpack/core/util/program_doc.hpp b/src/mlpack/core/util/program_doc.hpp
index f5de89fe5..296b15586 100644
--- a/src/mlpack/core/util/program_doc.hpp
+++ b/src/mlpack/core/util/program_doc.hpp
@@ -33,17 +33,32 @@ class ProgramDoc
    * will be returned.
    *
    * @param programName Short string representing the name of the program.
+   * @param shortDocumentation A short two-sentence description of the program,
+   *     what it does, and what it is useful for.
    * @param documentation Long string containing documentation on how to use the
    *     program and what it is.  No newline characters are necessary; this is
    *     taken care of by CLI later.
+   * @param seeAlso A set of pairs of strings with useful "see also"
+   *     information; each pair is <description, url>.
    */
   ProgramDoc(const std::string& programName,
-             const std::function<std::string()>& documentation);
+             const std::string& shortDocumentation,
+             const std::function<std::string()>& documentation,
+             const std::vector<std::pair<std::string, std::string>>& seeAlso);
+
+  /**
+   * Construct an empty ProgramDoc object.  (This is not meant to be used!)
+   */
+  ProgramDoc();
 
   //! The name of the program.
   std::string programName;
+  //! The short documentation for the program.
+  std::string shortDocumentation;
   //! Documentation for what the program does.
   std::function<std::string()> documentation;
+  //! Set of see also information.
+  std::vector<std::pair<std::string, std::string>> seeAlso;
 };
 
 } // namespace util
diff --git a/src/mlpack/methods/CMakeLists.txt b/src/mlpack/methods/CMakeLists.txt
index 4e6fc3df9..360e79f93 100644
--- a/src/mlpack/methods/CMakeLists.txt
+++ b/src/mlpack/methods/CMakeLists.txt
@@ -74,3 +74,7 @@ endforeach()
 
 set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 set(MLPACK_PYXS ${MLPACK_PYXS} PARENT_SCOPE)
+set(MARKDOWN_SRCS ${MARKDOWN_SRCS} PARENT_SCOPE)
+set(MARKDOWN_NAMES ${MARKDOWN_NAMES} PARENT_SCOPE)
+set(MARKDOWN_NAME_CATEGORIES ${MARKDOWN_NAME_CATEGORIES} PARENT_SCOPE)
+set(MARKDOWN_ALL_LANGUAGES_LIST ${MARKDOWN_ALL_LANGUAGES_LIST} PARENT_SCOPE)
diff --git a/src/mlpack/methods/adaboost/CMakeLists.txt b/src/mlpack/methods/adaboost/CMakeLists.txt
index 5505f057f..3e85237e0 100644
--- a/src/mlpack/methods/adaboost/CMakeLists.txt
+++ b/src/mlpack/methods/adaboost/CMakeLists.txt
@@ -20,3 +20,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(adaboost)
 add_python_binding(adaboost)
+add_markdown_docs(adaboost "cli;python" "classification")
diff --git a/src/mlpack/methods/adaboost/adaboost_main.cpp b/src/mlpack/methods/adaboost/adaboost_main.cpp
index 0c3be12e2..ced3d3c1f 100644
--- a/src/mlpack/methods/adaboost/adaboost_main.cpp
+++ b/src/mlpack/methods/adaboost/adaboost_main.cpp
@@ -46,7 +46,14 @@ using namespace mlpack::decision_stump;
 using namespace mlpack::perceptron;
 using namespace mlpack::util;
 
-PROGRAM_INFO("AdaBoost", "This program implements the AdaBoost (or Adaptive "
+PROGRAM_INFO("AdaBoost",
+    // Short description.
+    "An implementation of the AdaBoost.MH (Adaptive Boosting) algorithm for "
+    "classification.  This can be used to train an AdaBoost model on labeled "
+    "data or use an existing AdaBoost model to predict the classes of new "
+    "points.",
+    // Long description.
+    "This program implements the AdaBoost (or Adaptive "
     "Boosting) algorithm. The variant of AdaBoost implemented here is "
     "AdaBoost.MH. It uses a weak learner, either decision stumps or "
     "perceptrons, and over many iterations, creates a strong learner that is a "
@@ -72,7 +79,7 @@ PROGRAM_INFO("AdaBoost", "This program implements the AdaBoost (or Adaptive "
     "classes for each point in the test dataset are output to the " +
     PRINT_PARAM_STRING("output") + " output parameter.  The AdaBoost model "
     "itself is output to the " + PRINT_PARAM_STRING("output_model") +
-    "output parameter."
+    " output parameter."
     "\n\n"
     "For example, to run AdaBoost on an input dataset " +
     PRINT_DATASET("data") + " with perceptrons as the weak learner type, "
@@ -88,7 +95,15 @@ PROGRAM_INFO("AdaBoost", "This program implements the AdaBoost (or Adaptive "
     PRINT_DATASET("predictions") + " with the following command: "
     "\n\n" +
     PRINT_CALL("adaboost", "input_model", "model", "test", "test_data",
-        "output", "predictions"));
+        "output", "predictions"),
+    // See also...
+    SEE_ALSO("AdaBoost on Wikipedia", "https://en.wikipedia.org/wiki/AdaBoost"),
+    SEE_ALSO("Improved boosting algorithms using confidence-rated predictions "
+        "(pdf)", "http://rob.schapire.net/papers/SchapireSi98.pdf"),
+    SEE_ALSO("Perceptron", "#perceptron"),
+    SEE_ALSO("Decision Stump", "#decision_stump"),
+    SEE_ALSO("mlpack::adaboost::AdaBoost C++ class documentation",
+        "@doxygen/classmlpack_1_1adaboost_1_1AdaBoost.html"));
 
 // Input for training.
 PARAM_MATRIX_IN("training", "Dataset for training AdaBoost.", "t");
diff --git a/src/mlpack/methods/approx_kfn/CMakeLists.txt b/src/mlpack/methods/approx_kfn/CMakeLists.txt
index da20af02b..be1a5c4f0 100644
--- a/src/mlpack/methods/approx_kfn/CMakeLists.txt
+++ b/src/mlpack/methods/approx_kfn/CMakeLists.txt
@@ -21,3 +21,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 # This program computes approximate furthest neighbors.
 add_cli_executable(approx_kfn)
 add_python_binding(approx_kfn)
+add_markdown_docs(approx_kfn "cli;python" "geometry")
diff --git a/src/mlpack/methods/approx_kfn/approx_kfn_main.cpp b/src/mlpack/methods/approx_kfn/approx_kfn_main.cpp
index 9adce050e..7091536fd 100644
--- a/src/mlpack/methods/approx_kfn/approx_kfn_main.cpp
+++ b/src/mlpack/methods/approx_kfn/approx_kfn_main.cpp
@@ -22,6 +22,12 @@ using namespace mlpack::util;
 using namespace std;
 
 PROGRAM_INFO("Approximate furthest neighbor search",
+    // Short description.
+    "An implementation of two strategies for furthest neighbor search.  This "
+    "can be used to compute the furthest neighbor of query point(s) from a set "
+    "of points; furthest neighbor models can be saved and reused with future "
+    "query point(s).",
+    // Long description.
     "This program implements two strategies for furthest neighbor search. "
     "These strategies are:"
     "\n\n"
@@ -86,7 +92,18 @@ PROGRAM_INFO("Approximate furthest neighbor search",
     PRINT_DATASET("neighbors") + " by calling"
     "\n\n" +
     PRINT_CALL("approx_kfn", "input_model", "model", "query", "new_query_set",
-        "k", 3, "neighbors", "neighbors"));
+        "k", 3, "neighbors", "neighbors"),
+    SEE_ALSO("k-furthest-neighbor search", "#kfn"),
+    SEE_ALSO("k-nearest-neighbor search", "#knn"),
+    SEE_ALSO("Fast approximate furthest neighbors with data-dependent candidate"
+        " selection (pdf)", "http://ratml.org/pub/pdf/2016fast.pdf"),
+    SEE_ALSO("Approximate furthest neighbor in high dimensions (pdf)",
+        "https://pdfs.semanticscholar.org/a4b5/7b9cbf37201fb1d9a56c0f4eefad0466"
+        "9c20.pdf"),
+    SEE_ALSO("mlpack::neighbor::QDAFN class documentation",
+        "@doxygen/classmlpack_1_1neighbor_1_1QDAFN.html"),
+    SEE_ALSO("mlpack::neighbor::DrusillaSelect class documentation",
+        "@doxygen/classmlpack_1_1neighbor_1_1DrusillaSelect.html"));
 
 PARAM_MATRIX_IN("reference", "Matrix containing the reference dataset.", "r");
 PARAM_MATRIX_IN("query", "Matrix containing query points.", "q");
diff --git a/src/mlpack/methods/cf/CMakeLists.txt b/src/mlpack/methods/cf/CMakeLists.txt
index 01796f29d..976304458 100644
--- a/src/mlpack/methods/cf/CMakeLists.txt
+++ b/src/mlpack/methods/cf/CMakeLists.txt
@@ -19,3 +19,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(cf)
 add_python_binding(cf)
+add_markdown_docs(cf "cli;python" "misc. / other")
diff --git a/src/mlpack/methods/cf/cf_main.cpp b/src/mlpack/methods/cf/cf_main.cpp
index 237fc7267..743e9007c 100644
--- a/src/mlpack/methods/cf/cf_main.cpp
+++ b/src/mlpack/methods/cf/cf_main.cpp
@@ -30,7 +30,13 @@ using namespace mlpack::util;
 using namespace std;
 
 // Document program.
-PROGRAM_INFO("Collaborative Filtering", "This program performs collaborative "
+PROGRAM_INFO("Collaborative Filtering",
+    // Short description.
+    "An implementation of several collaborative filtering (CF) techniques for "
+    "recommender systems.  This can be used to train a new CF model, or use an"
+    " existing CF model to compute recommendations.",
+    // Long description.
+    "This program performs collaborative "
     "filtering (CF) on the given dataset. Given a list of user, item and "
     "preferences (the " + PRINT_PARAM_STRING("training") + " parameter), "
     "the program will perform a matrix decomposition and then can perform a "
@@ -53,7 +59,7 @@ PROGRAM_INFO("Collaborative Filtering", "This program performs collaborative "
     "addition, the number of recommendations per user to generate can be "
     "specified with the " + PRINT_PARAM_STRING("recommendations") + " "
     "parameter, and the number of similar users (the size of the neighborhood) "
-    " to be considered when generating recommendations can be specified with "
+    "to be considered when generating recommendations can be specified with "
     "the " + PRINT_PARAM_STRING("neighborhood") + " parameter."
     "\n\n"
     "For performing the matrix decomposition, the following optimization "
@@ -83,7 +89,20 @@ PROGRAM_INFO("Collaborative Filtering", "This program performs collaborative "
     "call "
     "\n\n" +
     PRINT_CALL("cf", "input_model", "model", "query", "users",
-        "recommendations", 5, "output", "recommendations"));
+        "recommendations", 5, "output", "recommendations"),
+    SEE_ALSO("Collaborative filtering tutorial", "@doxygen/cftutorial.html"),
+    SEE_ALSO("Alternating Matrix Factorization tutorial",
+        "@doxygen/amftutorial.html"),
+    SEE_ALSO("Collaborative Filtering on Wikipedia",
+        "https://en.wikipedia.org/wiki/Collaborative_filtering"),
+    SEE_ALSO("Matrix factorization on Wikipedia",
+        "https://en.wikipedia.org/wiki/Matrix_factorization_"
+        "(recommender_systems)"),
+    SEE_ALSO("Matrix factorization techniques for recommender systems (pdf)",
+        "http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.441.3234"
+        "&rep=rep1&type=pdf"),
+    SEE_ALSO("mlpack::cf::CFType class documentation",
+        "@doxygen/classmlpack_1_1cf_1_1CFType.html"));
 
 // Parameters for training a model.
 PARAM_MATRIX_IN("training", "Input dataset to perform CF on.", "t");
diff --git a/src/mlpack/methods/dbscan/CMakeLists.txt b/src/mlpack/methods/dbscan/CMakeLists.txt
index 70939c9d0..47b79d782 100644
--- a/src/mlpack/methods/dbscan/CMakeLists.txt
+++ b/src/mlpack/methods/dbscan/CMakeLists.txt
@@ -17,3 +17,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(dbscan)
 add_python_binding(dbscan)
+add_markdown_docs(dbscan "cli;python" "clustering")
diff --git a/src/mlpack/methods/dbscan/dbscan_main.cpp b/src/mlpack/methods/dbscan/dbscan_main.cpp
index c4afc3c85..04bcfc85f 100644
--- a/src/mlpack/methods/dbscan/dbscan_main.cpp
+++ b/src/mlpack/methods/dbscan/dbscan_main.cpp
@@ -27,6 +27,10 @@ using namespace mlpack::util;
 using namespace std;
 
 PROGRAM_INFO("DBSCAN clustering",
+    // Short description.
+    "An implementation of DBSCAN clustering.  Given a dataset, this can "
+    "compute and return a clustering of that dataset.",
+    // Long description.
     "This program implements the DBSCAN algorithm for clustering using "
     "accelerated tree-based range search.  The type of tree that is used "
     "may be parameterized, or brute-force range search may also be used."
@@ -59,7 +63,13 @@ PROGRAM_INFO("DBSCAN clustering",
     PRINT_DATASET("input") + " with a radius of 0.5 and a minimum cluster size"
     " of 5 is given below:"
     "\n\n" +
-    PRINT_CALL("dbscan", "input", "input", "epsilon", 0.5, "min_size", 5));
+    PRINT_CALL("dbscan", "input", "input", "epsilon", 0.5, "min_size", 5),
+    SEE_ALSO("DBSCAN on Wikipedia", "https://en.wikipedia.org/wiki/DBSCAN"),
+    SEE_ALSO("A density-based algorithm for discovering clusters in large "
+        "spatial databases with noise (pdf)",
+        "http://www.aaai.org/Papers/KDD/1996/KDD96-037.pdf"),
+    SEE_ALSO("mlpack::dbscan::DBSCAN class documentation",
+        "@doxygen/classmlpack_1_1dbscan_1_1DBSCAN.html"));
 
 PARAM_MATRIX_IN_REQ("input", "Input dataset to cluster.", "i");
 PARAM_UROW_OUT("assignments", "Output matrix for assignments of each "
diff --git a/src/mlpack/methods/decision_stump/CMakeLists.txt b/src/mlpack/methods/decision_stump/CMakeLists.txt
index e9d9c04c1..a11b0048e 100644
--- a/src/mlpack/methods/decision_stump/CMakeLists.txt
+++ b/src/mlpack/methods/decision_stump/CMakeLists.txt
@@ -18,3 +18,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(decision_stump)
 add_python_binding(decision_stump)
+add_markdown_docs(decision_stump "cli;python" "classification")
diff --git a/src/mlpack/methods/decision_stump/decision_stump_main.cpp b/src/mlpack/methods/decision_stump/decision_stump_main.cpp
index aa4d9a67d..6f8f308af 100644
--- a/src/mlpack/methods/decision_stump/decision_stump_main.cpp
+++ b/src/mlpack/methods/decision_stump/decision_stump_main.cpp
@@ -22,6 +22,11 @@ using namespace std;
 using namespace arma;
 
 PROGRAM_INFO("Decision Stump",
+    // Short description.
+    "An implementation of a decision stump, which is a single-level decision "
+    "tree.  Given labeled data, a new decision stump can be trained; or, an "
+    "existing decision stump can be used to classify points.",
+    // Long description.
     "This program implements a decision stump, which is a single-level decision"
     " tree.  The decision stump will split on one dimension of the input data, "
     "and will split into multiple buckets.  The dimension and bins are selected"
@@ -61,7 +66,12 @@ PROGRAM_INFO("Decision Stump",
     "\n\n"
     "After training, a decision stump can be saved with the " +
     PRINT_PARAM_STRING("output_model") + " output parameter.  That stump may "
-    "later be re-used in subsequent calls to this program (or others).");
+    "later be re-used in subsequent calls to this program (or others).",
+    SEE_ALSO("Decision tree", "#decision_tree"),
+    SEE_ALSO("Decision stumps on Wikipedia",
+        "https://en.wikipedia.org/wiki/Decision_stump"),
+    SEE_ALSO("mlpack::decision_stump::DecisionStump class documentation",
+        "@doxygen/classmlpack_1_1decision__stump_1_1DecisionStump.html"));
 
 // Datasets we might load.
 PARAM_MATRIX_IN("training", "The dataset to train on.", "t");
diff --git a/src/mlpack/methods/decision_tree/CMakeLists.txt b/src/mlpack/methods/decision_tree/CMakeLists.txt
index 448e4d62e..3f46f0d95 100644
--- a/src/mlpack/methods/decision_tree/CMakeLists.txt
+++ b/src/mlpack/methods/decision_tree/CMakeLists.txt
@@ -27,3 +27,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(decision_tree)
 add_python_binding(decision_tree)
+add_markdown_docs(decision_tree "cli;python" "classification")
diff --git a/src/mlpack/methods/decision_tree/decision_tree_main.cpp b/src/mlpack/methods/decision_tree/decision_tree_main.cpp
index 0e4be96c0..312b8e962 100644
--- a/src/mlpack/methods/decision_tree/decision_tree_main.cpp
+++ b/src/mlpack/methods/decision_tree/decision_tree_main.cpp
@@ -21,6 +21,12 @@ using namespace mlpack::data;
 using namespace mlpack::util;
 
 PROGRAM_INFO("Decision tree",
+    // Short description.
+    "An implementation of an ID3-style decision tree for classification, which"
+    " supports categorical data.  Given labeled data with numeric or "
+    "categorical features, a decision tree can be trained and saved; or, an "
+    "existing decision tree can be used for classification on new points.",
+    // Long description.
     "Train and evaluate using a decision tree.  Given a dataset containing "
     "numeric or categorical features, and associated labels for each point in "
     "the dataset, this program can train a decision tree on that data."
@@ -70,7 +76,15 @@ PROGRAM_INFO("Decision tree",
     PRINT_DATASET("predictions") + ", one could call "
     "\n\n" +
     PRINT_CALL("decision_tree", "input_model", "tree", "test", "test_set",
-        "test_labels", "test_labels", "predictions", "predictions"));
+        "test_labels", "test_labels", "predictions", "predictions"),
+    SEE_ALSO("Decision stump", "#decision_stump"),
+    SEE_ALSO("Random forest", "#random_forest"),
+    SEE_ALSO("Decision trees on Wikipedia",
+        "https://en.wikipedia.org/wiki/Decision_tree_learning"),
+    SEE_ALSO("Induction of Decision Trees (pdf)",
+        "https://link.springer.com/content/pdf/10.1007/BF00116251.pdf"),
+    SEE_ALSO("mlpack::tree::DecisionTree class documentation",
+        "@doxygen/classmlpack_1_1tree_1_1DecisionTree.html"));
 
 // Datasets.
 PARAM_MATRIX_AND_INFO_IN("training", "Training dataset (may be categorical).",
diff --git a/src/mlpack/methods/det/CMakeLists.txt b/src/mlpack/methods/det/CMakeLists.txt
index 762c7b11c..58b402c7c 100644
--- a/src/mlpack/methods/det/CMakeLists.txt
+++ b/src/mlpack/methods/det/CMakeLists.txt
@@ -21,3 +21,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(det)
 add_python_binding(det)
+add_markdown_docs(det "cli;python" "misc. / other")
diff --git a/src/mlpack/methods/det/det_main.cpp b/src/mlpack/methods/det/det_main.cpp
index 135baa703..df2db8169 100644
--- a/src/mlpack/methods/det/det_main.cpp
+++ b/src/mlpack/methods/det/det_main.cpp
@@ -20,6 +20,11 @@ using namespace mlpack::util;
 using namespace std;
 
 PROGRAM_INFO("Density Estimation With Density Estimation Trees",
+    // Short description.
+    "An implementation of density estimation trees for the density estimation "
+    "task.  Density estimation trees can be trained or used to predict the "
+    "density at locations given by query points.",
+    // Long description.
     "This program performs a number of functions related to Density Estimation "
     "Trees.  The optimal Density Estimation Tree (DET) can be trained on a set "
     "of data (specified by " + PRINT_PARAM_STRING("training") + ") using "
@@ -49,7 +54,15 @@ PROGRAM_INFO("Density Estimation With Density Estimation Trees",
     "trained on the given training points, or a tree given as the parameter " +
     PRINT_PARAM_STRING("input_model") + ".  The density estimates for the test"
     " points may be saved using the " +
-    PRINT_PARAM_STRING("test_set_estimates") + " output parameter.");
+    PRINT_PARAM_STRING("test_set_estimates") + " output parameter.",
+    SEE_ALSO("Density estimation tree (DET) tutorial",
+        "@doxygen/dettutorial.html"),
+    SEE_ALSO("Density estimation on Wikipedia",
+        "https://en.wikipedia.org/wiki/Density_estimation"),
+    SEE_ALSO("Density estimation trees (pdf)",
+        "http://www.mlpack.org/papers/det.pdf"),
+    SEE_ALSO("mlpack::tree::DTree class documentation",
+        "@doxygen/classmlpack_1_1det_1_1DTree.html"));
 
 // Input data files.
 PARAM_MATRIX_IN("training", "The data set on which to build a density "
diff --git a/src/mlpack/methods/emst/CMakeLists.txt b/src/mlpack/methods/emst/CMakeLists.txt
index 73cd431cb..7a073a8f9 100644
--- a/src/mlpack/methods/emst/CMakeLists.txt
+++ b/src/mlpack/methods/emst/CMakeLists.txt
@@ -23,3 +23,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(emst)
 add_python_binding(emst)
+add_markdown_docs(emst "cli;python" "geometry")
diff --git a/src/mlpack/methods/emst/emst_main.cpp b/src/mlpack/methods/emst/emst_main.cpp
index 3b0f9de98..e60ba7b64 100644
--- a/src/mlpack/methods/emst/emst_main.cpp
+++ b/src/mlpack/methods/emst/emst_main.cpp
@@ -31,6 +31,10 @@
 #include "dtb.hpp"
 
 PROGRAM_INFO("Fast Euclidean Minimum Spanning Tree",
+    // Short description.
+    "An implementation of the Dual-Tree Boruvka algorithm for computing the "
+    "Euclidean minimum spanning tree of a set of input points.",
+    // Long description.
     "This program can compute the Euclidean minimum spanning tree of a set of "
     "input points using the dual-tree Boruvka algorithm."
     "\n\n"
@@ -56,7 +60,14 @@ PROGRAM_INFO("Fast Euclidean Minimum Spanning Tree",
     "The output matrix is a three-dimensional matrix, where each row indicates "
     "an edge.  The first dimension corresponds to the lesser index of the edge;"
     " the second dimension corresponds to the greater index of the edge; and "
-    "the third column corresponds to the distance between the two points.");
+    "the third column corresponds to the distance between the two points.",
+    SEE_ALSO("EMST Tutorial", "@doxygen/emst_tutorial.html"),
+    SEE_ALSO("Minimum spanning tree on Wikipedia",
+        "https://en.wikipedia.org/wiki/Minimum_spanning_tree"),
+    SEE_ALSO("Fast Euclidean Minimum Spanning Tree: Algorithm, Analysis, and "
+        "Applications (pdf)", "http://www.mlpack.org/papers/emst.pdf"),
+    SEE_ALSO("mlpack::emst::DualTreeBoruvka class documentation",
+        "@doxygen/classmlpack_1_1emst_1_1DualTreeBoruvka.html"));
 
 PARAM_MATRIX_IN_REQ("input", "Input data matrix.", "i");
 PARAM_MATRIX_OUT("output", "Output data.  Stored as an edge list.", "o");
diff --git a/src/mlpack/methods/fastmks/CMakeLists.txt b/src/mlpack/methods/fastmks/CMakeLists.txt
index d9a6a6a8e..e6b6e55c1 100644
--- a/src/mlpack/methods/fastmks/CMakeLists.txt
+++ b/src/mlpack/methods/fastmks/CMakeLists.txt
@@ -22,3 +22,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(fastmks)
 add_python_binding(fastmks)
+add_markdown_docs(fastmks "cli;python" "geometry")
diff --git a/src/mlpack/methods/fastmks/fastmks_main.cpp b/src/mlpack/methods/fastmks/fastmks_main.cpp
index 1d8e7e7fb..fea273d65 100644
--- a/src/mlpack/methods/fastmks/fastmks_main.cpp
+++ b/src/mlpack/methods/fastmks/fastmks_main.cpp
@@ -25,6 +25,12 @@ using namespace mlpack::metric;
 using namespace mlpack::util;
 
 PROGRAM_INFO("FastMKS (Fast Max-Kernel Search)",
+    // Short description.
+    "An implementation of the single-tree and dual-tree fast max-kernel search"
+    " (FastMKS) algorithm.  Given a set of reference points and a set of query"
+    " points, this can find the reference point with maximum kernel value for "
+    "each query point; trained models can be reused for future queries.",
+    // Long description.
     "This program will find the k maximum kernels of a set of points, "
     "using a query set and a reference set (which can optionally be the same "
     "set). More specifically, for each point in the query set, the k points in"
@@ -51,7 +57,14 @@ PROGRAM_INFO("FastMKS (Fast Max-Kernel Search)",
     "\n\n"
     "This program performs FastMKS using a cover tree.  The base used to build "
     "the cover tree can be specified with the " + PRINT_PARAM_STRING("base") +
-    " parameter.");
+    " parameter.",
+    SEE_ALSO("Fast max-kernel search tutorial (fastmks)",
+        "@doxygen/fmkstutorial.html"),
+    SEE_ALSO("k-nearest-neighbor search", "#knn"),
+    SEE_ALSO("Dual-tree Fast Exact Max-Kernel Search (pdf)",
+        "http://mlpack.org/papers/fmks.pdf"),
+    SEE_ALSO("mlpack::fastmks::FastMKS class documentation",
+        "@doxygen/classmlpack_1_1fastmks_1_1FastMKS.html"));
 
 // Model-building parameters.
 PARAM_MATRIX_IN("reference", "The reference dataset.", "r");
diff --git a/src/mlpack/methods/gmm/CMakeLists.txt b/src/mlpack/methods/gmm/CMakeLists.txt
index 74f9e8ace..964f490be 100644
--- a/src/mlpack/methods/gmm/CMakeLists.txt
+++ b/src/mlpack/methods/gmm/CMakeLists.txt
@@ -23,7 +23,12 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(gmm_train)
 add_python_binding(gmm_train)
+add_markdown_docs(gmm_train "cli;python" "clustering")
+
 add_cli_executable(gmm_generate)
 add_python_binding(gmm_generate)
+add_markdown_docs(gmm_generate "cli;python" "clustering")
+
 add_cli_executable(gmm_probability)
 add_python_binding(gmm_probability)
+add_markdown_docs(gmm_probability "cli;python" "clustering")
diff --git a/src/mlpack/methods/gmm/gmm_generate_main.cpp b/src/mlpack/methods/gmm/gmm_generate_main.cpp
index 784234941..4f3d10fdb 100644
--- a/src/mlpack/methods/gmm/gmm_generate_main.cpp
+++ b/src/mlpack/methods/gmm/gmm_generate_main.cpp
@@ -20,6 +20,10 @@ using namespace mlpack::gmm;
 using namespace mlpack::util;
 
 PROGRAM_INFO("GMM Sample Generator",
+    // Short description.
+    "A sample generator for pre-trained GMMs.  Given a pre-trained GMM, this "
+    "can sample new points randomly from that distribution.",
+    // Long description.
     "This program is able to generate samples from a pre-trained GMM (use "
     "gmm_train to train a GMM).  The pre-trained GMM must be specified with "
     "the " + PRINT_PARAM_STRING("input_model") + " parameter.  The number "
@@ -32,7 +36,13 @@ PROGRAM_INFO("GMM Sample Generator",
     "samples in " + PRINT_DATASET("samples") + ":"
     "\n\n" +
     PRINT_CALL("gmm_generate", "input_model", "gmm", "samples", 100, "output",
-        "samples"));
+        "samples"),
+    SEE_ALSO("@gmm_train", "#gmm_train"),
+    SEE_ALSO("@gmm_probability", "#gmm_probability"),
+    SEE_ALSO("Gaussian Mixture Models on Wikipedia",
+        "https://en.wikipedia.org/wiki/Mixture_model#Gaussian_mixture_model"),
+    SEE_ALSO("mlpack::gmm::GMM class documentation",
+        "@doxygen/classmlpack_1_1gmm_1_1GMM.html"));
 
 PARAM_MODEL_IN_REQ(GMM, "input_model", "Input GMM model to generate samples "
     "from.", "m");
diff --git a/src/mlpack/methods/gmm/gmm_probability_main.cpp b/src/mlpack/methods/gmm/gmm_probability_main.cpp
index f40c86628..6ba29585c 100644
--- a/src/mlpack/methods/gmm/gmm_probability_main.cpp
+++ b/src/mlpack/methods/gmm/gmm_probability_main.cpp
@@ -20,6 +20,11 @@ using namespace mlpack::gmm;
 using namespace mlpack::util;
 
 PROGRAM_INFO("GMM Probability Calculator",
+    // Short description.
+    "A probability calculator for GMMs.  Given a pre-trained GMM and a set of "
+    "points, this can compute the probability that each point is from the given"
+    " GMM.",
+    // Long description.
     "This program calculates the probability that given points came from a "
     "given GMM (that is, P(X | gmm)).  The GMM is specified with the " +
     PRINT_PARAM_STRING("input_model") + " parameter, and the points are "
@@ -33,7 +38,13 @@ PROGRAM_INFO("GMM Probability Calculator",
     PRINT_DATASET("probs") + ", the following command could be used:"
     "\n\n" +
     PRINT_CALL("gmm_probability", "input_model", "gmm", "input", "points",
-        "output", "probs"));
+        "output", "probs"),
+    SEE_ALSO("@gmm_train", "#gmm_train"),
+    SEE_ALSO("@gmm_generate", "#gmm_generate"),
+    SEE_ALSO("Gaussian Mixture Models on Wikipedia",
+        "https://en.wikipedia.org/wiki/Mixture_model#Gaussian_mixture_model"),
+    SEE_ALSO("mlpack::gmm::GMM class documentation",
+        "@doxygen/classmlpack_1_1gmm_1_1GMM.html"));
 
 PARAM_MODEL_IN_REQ(GMM, "input_model", "Input GMM to use as model.", "m");
 PARAM_MATRIX_IN_REQ("input", "Input matrix to calculate probabilities of.",
diff --git a/src/mlpack/methods/gmm/gmm_train_main.cpp b/src/mlpack/methods/gmm/gmm_train_main.cpp
index c331d04dc..a01adfcb0 100644
--- a/src/mlpack/methods/gmm/gmm_train_main.cpp
+++ b/src/mlpack/methods/gmm/gmm_train_main.cpp
@@ -26,6 +26,11 @@ using namespace mlpack::kmeans;
 using namespace std;
 
 PROGRAM_INFO("Gaussian Mixture Model (GMM) Training",
+    // Short description.
+    "An implementation of the EM algorithm for training Gaussian mixture "
+    "models (GMMs).  Given a dataset, this can train a GMM for future use "
+    "with other tools.",
+    // Long description.
     "This program takes a parametric estimate of a Gaussian mixture model (GMM)"
     " using the EM algorithm to find the maximum likelihood estimate.  The "
     "model may be saved and reused by other mlpack GMM tools."
@@ -84,7 +89,13 @@ PROGRAM_INFO("Gaussian Mixture Model (GMM) Training",
     ", the following command may be used: "
     "\n\n" +
     PRINT_CALL("gmm_train", "input_model", "gmm", "input", "data2",
-        "gaussians", 6, "output_model", "new_gmm"));
+        "gaussians", 6, "output_model", "new_gmm"),
+    SEE_ALSO("@gmm_generate", "#gmm_generate"),
+    SEE_ALSO("@gmm_probability", "#gmm_probability"),
+    SEE_ALSO("Gaussian Mixture Models on Wikipedia",
+        "https://en.wikipedia.org/wiki/Mixture_model#Gaussian_mixture_model"),
+    SEE_ALSO("mlpack::gmm::GMM class documentation",
+        "@doxygen/classmlpack_1_1gmm_1_1GMM.html"));
 
 // Parameters for training.
 PARAM_MATRIX_IN_REQ("input", "The training data on which the model will be "
diff --git a/src/mlpack/methods/hmm/CMakeLists.txt b/src/mlpack/methods/hmm/CMakeLists.txt
index 7add2baa3..7247430bb 100644
--- a/src/mlpack/methods/hmm/CMakeLists.txt
+++ b/src/mlpack/methods/hmm/CMakeLists.txt
@@ -21,9 +21,16 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(hmm_train)
 add_python_binding(hmm_train)
+add_markdown_docs(hmm_train "cli;python" "misc. / other")
+
 add_cli_executable(hmm_loglik)
 add_python_binding(hmm_loglik)
+add_markdown_docs(hmm_loglik "cli;python" "misc. / other")
+
 add_cli_executable(hmm_viterbi)
 add_python_binding(hmm_viterbi)
+add_markdown_docs(hmm_viterbi "cli;python" "misc. / other")
+
 add_cli_executable(hmm_generate)
 add_python_binding(hmm_generate)
+add_markdown_docs(hmm_generate "cli;python" "misc. / other")
diff --git a/src/mlpack/methods/hmm/hmm_generate_main.cpp b/src/mlpack/methods/hmm/hmm_generate_main.cpp
index 0efc8dc84..cbfbe30bf 100644
--- a/src/mlpack/methods/hmm/hmm_generate_main.cpp
+++ b/src/mlpack/methods/hmm/hmm_generate_main.cpp
@@ -29,8 +29,13 @@ using namespace mlpack::math;
 using namespace arma;
 using namespace std;
 
-PROGRAM_INFO("Hidden Markov Model (HMM) Sequence Generator", "This "
-    "utility takes an already-trained HMM, specified as the " +
+PROGRAM_INFO("Hidden Markov Model (HMM) Sequence Generator",
+    // Short description.
+    "A utility to generate random sequences from a pre-trained Hidden Markov "
+    "Model (HMM).  The length of the desired sequence can be specified, and a "
+    "random sequence of observations is returned.",
+    // Long description.
+    "This utility takes an already-trained HMM, specified as the " +
     PRINT_PARAM_STRING("model") + " parameter, and generates a random "
     "observation sequence and hidden state sequence based on its parameters. "
     "The observation sequence may be saved with the " +
@@ -47,7 +52,14 @@ PROGRAM_INFO("Hidden Markov Model (HMM) Sequence Generator", "This "
     PRINT_DATASET("states") + ", the following command may be used: "
     "\n\n" +
     PRINT_CALL("hmm_generate", "model", "hmm", "length", 150, "output",
-        "observations", "state", "states"));
+        "observations", "state", "states"),
+    SEE_ALSO("@hmm_train", "#hmm_train"),
+    SEE_ALSO("@hmm_loglik", "#hmm_loglik"),
+    SEE_ALSO("@hmm_viterbi", "#hmm_viterbi"),
+    SEE_ALSO("Hidden Mixture Models on Wikipedia",
+        "https://en.wikipedia.org/wiki/Hidden_Markov_model"),
+    SEE_ALSO("mlpack::hmm::HMM class documentation",
+        "@doxygen/classmlpack_1_1hmm_1_1HMM.html"));
 
 PARAM_MODEL_IN_REQ(HMMModel, "model", "Trained HMM to generate sequences with.",
     "m");
diff --git a/src/mlpack/methods/hmm/hmm_loglik_main.cpp b/src/mlpack/methods/hmm/hmm_loglik_main.cpp
index 6ebb3cbef..23a482603 100644
--- a/src/mlpack/methods/hmm/hmm_loglik_main.cpp
+++ b/src/mlpack/methods/hmm/hmm_loglik_main.cpp
@@ -26,8 +26,14 @@ using namespace mlpack::gmm;
 using namespace arma;
 using namespace std;
 
-PROGRAM_INFO("Hidden Markov Model (HMM) Sequence Log-Likelihood", "This "
-    "utility takes an already-trained HMM, specified with the " +
+PROGRAM_INFO("Hidden Markov Model (HMM) Sequence Log-Likelihood",
+    // Short description.
+    "A utility for computing the log-likelihood of a sequence for Hidden Markov"
+    " Models (HMMs).  Given a pre-trained HMM and an observation sequence, this"
+    " computes and returns the log-likelihood of that sequence being observed "
+    "from that HMM.",
+    // Long description.
+    "This utility takes an already-trained HMM, specified with the " +
     PRINT_PARAM_STRING("input_model") + " parameter, and evaluates the "
     "log-likelihood of a sequence of observations, given with the " +
     PRINT_PARAM_STRING("input") + " parameter.  The computed log-likelihood is"
@@ -37,7 +43,14 @@ PROGRAM_INFO("Hidden Markov Model (HMM) Sequence Log-Likelihood", "This "
     PRINT_DATASET("seq") + " with the pre-trained HMM " + PRINT_MODEL("hmm") +
     ", the following command may be used: "
     "\n\n" +
-    PRINT_CALL("hmm_loglik", "input", "seq", "input_model", "hmm"));
+    PRINT_CALL("hmm_loglik", "input", "seq", "input_model", "hmm"),
+    SEE_ALSO("@hmm_train", "#hmm_train"),
+    SEE_ALSO("@hmm_generate", "#hmm_generate"),
+    SEE_ALSO("@hmm_viterbi", "#hmm_viterbi"),
+    SEE_ALSO("Hidden Mixture Models on Wikipedia",
+        "https://en.wikipedia.org/wiki/Hidden_Markov_model"),
+    SEE_ALSO("mlpack::hmm::HMM class documentation",
+        "@doxygen/classmlpack_1_1hmm_1_1HMM.html"));
 
 PARAM_MATRIX_IN_REQ("input", "File containing observations,", "i");
 PARAM_MODEL_IN_REQ(HMMModel, "input_model", "File containing HMM.", "m");
diff --git a/src/mlpack/methods/hmm/hmm_train_main.cpp b/src/mlpack/methods/hmm/hmm_train_main.cpp
index 109cfbddd..3e21085a1 100644
--- a/src/mlpack/methods/hmm/hmm_train_main.cpp
+++ b/src/mlpack/methods/hmm/hmm_train_main.cpp
@@ -27,9 +27,15 @@ using namespace mlpack::math;
 using namespace arma;
 using namespace std;
 
-PROGRAM_INFO("Hidden Markov Model (HMM) Training", "This program allows a "
-    "Hidden Markov Model to be trained on labeled or unlabeled data.  It "
-    "support three types of HMMs: discrete HMMs, Gaussian HMMs, or GMM HMMs."
+PROGRAM_INFO("Hidden Markov Model (HMM) Training",
+    // Short description.
+    "An implementation of training algorithms for Hidden Markov Models (HMMs). "
+    "Given labeled or unlabeled data, an HMM can be trained for further use "
+    "with other mlpack HMM tools.",
+    // Long description.
+    "This program allows a Hidden Markov Model to be trained on labeled or "
+    "unlabeled data.  It supports three types of HMMs: discrete HMMs, "
+    "Gaussian HMMs, or GMM HMMs."
     "\n\n"
     "Either one input sequence can be specified (with --input_file), or, a "
     "file containing files in which input sequences can be found (when "
@@ -46,7 +52,14 @@ PROGRAM_INFO("Hidden Markov Model (HMM) Training", "This program allows a "
     "\n\n"
     "Optionally, a pre-created HMM model can be used as a guess for the "
     "transition matrix and emission probabilities; this is specifiable with "
-    "--model_file.");
+    "--model_file.",
+    SEE_ALSO("@hmm_generate", "#hmm_generate"),
+    SEE_ALSO("@hmm_loglik", "#hmm_loglik"),
+    SEE_ALSO("@hmm_viterbi", "#hmm_viterbi"),
+    SEE_ALSO("Hidden Mixture Models on Wikipedia",
+        "https://en.wikipedia.org/wiki/Hidden_Markov_model"),
+    SEE_ALSO("mlpack::hmm::HMM class documentation",
+        "@doxygen/classmlpack_1_1hmm_1_1HMM.html"));
 
 PARAM_STRING_IN_REQ("input_file", "File containing input observations.", "i");
 PARAM_STRING_IN("type", "Type of HMM: discrete | gaussian | gmm.", "t",
diff --git a/src/mlpack/methods/hmm/hmm_viterbi_main.cpp b/src/mlpack/methods/hmm/hmm_viterbi_main.cpp
index d0e1168b3..1fa3c5f58 100644
--- a/src/mlpack/methods/hmm/hmm_viterbi_main.cpp
+++ b/src/mlpack/methods/hmm/hmm_viterbi_main.cpp
@@ -27,8 +27,14 @@ using namespace mlpack::gmm;
 using namespace arma;
 using namespace std;
 
-PROGRAM_INFO("Hidden Markov Model (HMM) Viterbi State Prediction", "This "
-    "utility takes an already-trained HMM, specified as " +
+PROGRAM_INFO("Hidden Markov Model (HMM) Viterbi State Prediction",
+    // Short description.
+    "A utility for computing the most probable hidden state sequence for Hidden"
+    " Markov Models (HMMs).  Given a pre-trained HMM and an observed sequence, "
+    "this uses the Viterbi algorithm to compute and return the most probable "
+    "hidden state sequence.",
+    // Long description.
+    "This utility takes an already-trained HMM, specified as " +
     PRINT_PARAM_STRING("input_model") + ", and evaluates the most probable "
     "hidden state sequence of a given sequence of observations (specified as "
     "'" + PRINT_PARAM_STRING("input") + ", using the Viterbi algorithm.  The "
@@ -41,7 +47,14 @@ PROGRAM_INFO("Hidden Markov Model (HMM) Viterbi State Prediction", "This "
     ", the following command could be used:"
     "\n\n" +
     PRINT_CALL("hmm_viterbi", "input", "obs", "input_model", "hmm", "output",
-        "states"));
+        "states"),
+    SEE_ALSO("@hmm_train", "#hmm_train"),
+    SEE_ALSO("@hmm_generate", "#hmm_generate"),
+    SEE_ALSO("@hmm_loglik", "#hmm_loglik"),
+    SEE_ALSO("Hidden Mixture Models on Wikipedia",
+        "https://en.wikipedia.org/wiki/Hidden_Markov_model"),
+    SEE_ALSO("mlpack::hmm::HMM class documentation",
+        "@doxygen/classmlpack_1_1hmm_1_1HMM.html"));
 
 PARAM_MATRIX_IN_REQ("input", "Matrix containing observations,", "i");
 PARAM_MODEL_IN_REQ(HMMModel, "input_model", "Trained HMM to use.", "m");
diff --git a/src/mlpack/methods/hoeffding_trees/CMakeLists.txt b/src/mlpack/methods/hoeffding_trees/CMakeLists.txt
index b573a19c6..2a94415b3 100644
--- a/src/mlpack/methods/hoeffding_trees/CMakeLists.txt
+++ b/src/mlpack/methods/hoeffding_trees/CMakeLists.txt
@@ -30,3 +30,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(hoeffding_tree)
 add_python_binding(hoeffding_tree)
+add_markdown_docs(hoeffding_tree "cli;python" "classification")
diff --git a/src/mlpack/methods/hoeffding_trees/hoeffding_tree_main.cpp b/src/mlpack/methods/hoeffding_trees/hoeffding_tree_main.cpp
index 46437b4ad..45b58713c 100644
--- a/src/mlpack/methods/hoeffding_trees/hoeffding_tree_main.cpp
+++ b/src/mlpack/methods/hoeffding_trees/hoeffding_tree_main.cpp
@@ -26,6 +26,12 @@ using namespace mlpack::data;
 using namespace mlpack::util;
 
 PROGRAM_INFO("Hoeffding trees",
+    // Short description.
+    "An implementation of Hoeffding trees, a form of streaming decision tree "
+    "for classification.  Given labeled data, a Hoeffding tree can be trained "
+    "and saved for later use, or a pre-trained Hoeffding tree can be used for "
+    "predicting the classifications of new points.",
+    // Long description.
     "This program implements Hoeffding trees, a form of streaming decision tree"
     " suited best for large (or streaming) datasets.  This program supports "
     "both categorical and numeric data.  Given an input dataset, this program "
@@ -70,7 +76,13 @@ PROGRAM_INFO("Hoeffding trees",
     PRINT_DATASET("class_probs") + " with the following command: "
     "\n\n" +
     PRINT_CALL("hoeffding_tree", "input_model", "tree", "test", "test_set",
-        "predictions", "predictions", "probabilities", "class_probs"));
+        "predictions", "predictions", "probabilities", "class_probs"),
+    SEE_ALSO("@decision_tree", "#decision_tree"),
+    SEE_ALSO("@random_forest", "#random_forest"),
+    SEE_ALSO("Mining High-Speed Data Streams (pdf)",
+        "http://dm.cs.washington.edu/papers/vfdt-kdd00.pdf"),
+    SEE_ALSO("mlpack::tree::HoeffdingTree class documentation",
+        "@doxygen/classmlpack_1_1tree_1_1HoeffdingTree.html"));
 
 PARAM_MATRIX_AND_INFO_IN("training", "Training dataset (may be categorical).",
     "t");
diff --git a/src/mlpack/methods/kernel_pca/CMakeLists.txt b/src/mlpack/methods/kernel_pca/CMakeLists.txt
index c14a9f3ec..9bd0cd3a0 100644
--- a/src/mlpack/methods/kernel_pca/CMakeLists.txt
+++ b/src/mlpack/methods/kernel_pca/CMakeLists.txt
@@ -18,3 +18,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(kernel_pca)
 add_python_binding(kernel_pca)
+add_markdown_docs(kernel_pca "cli;python" "transformations")
diff --git a/src/mlpack/methods/kernel_pca/kernel_pca_main.cpp b/src/mlpack/methods/kernel_pca/kernel_pca_main.cpp
index a9b3478e0..a736c27ed 100644
--- a/src/mlpack/methods/kernel_pca/kernel_pca_main.cpp
+++ b/src/mlpack/methods/kernel_pca/kernel_pca_main.cpp
@@ -41,6 +41,11 @@ using namespace std;
 using namespace arma;
 
 PROGRAM_INFO("Kernel Principal Components Analysis",
+    // Short description.
+    "An implementation of Kernel Principal Components Analysis (KPCA).  This "
+    "can be used to perform nonlinear dimensionality reduction or preprocessing"
+    " on a given dataset.",
+    // Long description.
     "This program performs Kernel Principal Components Analysis (KPCA) on the "
     "specified dataset with the specified kernel.  This will transform the "
     "data onto the kernel principal components, and optionally reduce the "
@@ -93,7 +98,13 @@ PROGRAM_INFO("Kernel Principal Components Analysis",
     "the kernel matrix; to specify the sampling scheme, the " +
     PRINT_PARAM_STRING("sampling") + " parameter is used.  The "
     "sampling scheme for the Nystr\u00F6m method can be chosen from the "
-    "following list: 'kmeans', 'random', 'ordered'.");
+    "following list: 'kmeans', 'random', 'ordered'.",
+    SEE_ALSO("Kernel principal component analysis on Wikipedia",
+        "https://en.wikipedia.org/wiki/Kernel_principal_component_analysis"),
+    SEE_ALSO("Kernel Principal Component Analysis (pdf)",
+        "http://pca.narod.ru/scholkopf_kernel.pdf"),
+    SEE_ALSO("mlpack::kpca::KernelPCA class documentation",
+        "@doxygen/classmlpack_1_1kpca_1_1KernelPCA.html"));
 
 PARAM_MATRIX_IN_REQ("input", "Input dataset to perform KPCA on.", "i");
 PARAM_MATRIX_OUT("output", "Matrix to save modified dataset to.", "o");
@@ -107,9 +118,9 @@ PARAM_INT_IN("new_dimensionality", "If not 0, reduce the dimensionality of "
 PARAM_FLAG("center", "If set, the transformed data will be centered about the "
     "origin.", "c");
 
-PARAM_FLAG("nystroem_method", "If set, the nystroem method will be used.", "n");
+PARAM_FLAG("nystroem_method", "If set, the Nystroem method will be used.", "n");
 
-PARAM_STRING_IN("sampling", "Sampling scheme to use for the nystroem method: "
+PARAM_STRING_IN("sampling", "Sampling scheme to use for the Nystroem method: "
     "'kmeans', 'random', 'ordered'", "s", "kmeans");
 
 PARAM_DOUBLE_IN("kernel_scale", "Scale, for 'hyptan' kernel.", "S", 1.0);
diff --git a/src/mlpack/methods/kmeans/CMakeLists.txt b/src/mlpack/methods/kmeans/CMakeLists.txt
index bf4990083..9cfec0fdc 100644
--- a/src/mlpack/methods/kmeans/CMakeLists.txt
+++ b/src/mlpack/methods/kmeans/CMakeLists.txt
@@ -40,3 +40,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(kmeans)
 add_python_binding(kmeans)
+add_markdown_docs(kmeans "cli;python" "clustering")
diff --git a/src/mlpack/methods/kmeans/kmeans_main.cpp b/src/mlpack/methods/kmeans/kmeans_main.cpp
index b0319ccb5..9d3f40c7d 100644
--- a/src/mlpack/methods/kmeans/kmeans_main.cpp
+++ b/src/mlpack/methods/kmeans/kmeans_main.cpp
@@ -28,11 +28,17 @@ using namespace mlpack::util;
 using namespace std;
 
 // Define parameters for the executable.
-PROGRAM_INFO("K-Means Clustering", "This program performs K-Means clustering "
-    "on the given dataset.  It can return the learned cluster assignments, and "
-    "the centroids of the clusters.  Empty clusters are not allowed by default;"
-    " when a cluster becomes empty, the point furthest from the centroid of the"
-    " cluster with maximum variance is taken to fill that cluster."
+PROGRAM_INFO("K-Means Clustering",
+    // Short description.
+    "An implementation of several strategies for efficient k-means clustering. "
+    "Given a dataset and a value of k, this computes and returns a k-means "
+    "clustering on that data.",
+    // Long description.
+    "This program performs K-Means clustering on the given dataset.  It can "
+    "return the learned cluster assignments, and the centroids of the clusters."
+    "  Empty clusters are not allowed by default; when a cluster becomes empty,"
+    " the point furthest from the centroid of the cluster with maximum variance"
+    " is taken to fill that cluster."
     "\n\n"
     "Optionally, the Bradley and Fayyad approach (\"Refining initial points for"
     " k-means clustering\", 1998) can be used to select initial points by "
@@ -85,7 +91,21 @@ PROGRAM_INFO("K-Means Clustering", "This program performs K-Means clustering "
     "following command may be used:"
     "\n\n" +
     PRINT_CALL("kmeans", "input", "data", "initial_centroids", "initial",
-        "clusters", 10, "max_iterations", 500, "centroid", "final"));
+        "clusters", 10, "max_iterations", 500, "centroid", "final"),
+    SEE_ALSO("K-Means tutorial", "@doxygen/kmtutorial.html"),
+    SEE_ALSO("@dbscan", "#dbscan"),
+    SEE_ALSO("Using the triangle inequality to accelerate k-means (pdf)",
+        "http://www.aaai.org/Papers/ICML/2003/ICML03-022.pdf"),
+    SEE_ALSO("Making k-means even faster (pdf)",
+        "http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.586.2554"
+        "&rep=rep1&type=pdf"),
+    SEE_ALSO("Accelerating exact k-means algorithms with geometric reasoning "
+        "(pdf)", "http://reports-archive.adm.cs.cmu.edu/anon/anon/usr/ftp/"
+        "usr0/ftp/2000/CMU-CS-00-105.pdf"),
+    SEE_ALSO("A dual-tree algorithm for fast k-means clustering with large k "
+        "(pdf)", "http://www.ratml.org/pub/pdf/2017dual.pdf"),
+    SEE_ALSO("mlpack::kmeans::KMeans class documentation",
+        "@doxygen/classmlpack_1_1kmeans_1_1KMeans.html"));
 
 // Required options.
 PARAM_MATRIX_IN_REQ("input", "Input dataset to perform clustering on.", "i");
diff --git a/src/mlpack/methods/lars/CMakeLists.txt b/src/mlpack/methods/lars/CMakeLists.txt
index 1388f24be..7e4cb7890 100644
--- a/src/mlpack/methods/lars/CMakeLists.txt
+++ b/src/mlpack/methods/lars/CMakeLists.txt
@@ -16,3 +16,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(lars)
 add_python_binding(lars)
+add_markdown_docs(lars "cli;python" "regression")
diff --git a/src/mlpack/methods/lars/lars_main.cpp b/src/mlpack/methods/lars/lars_main.cpp
index 040c5f0c3..e0b2f38f3 100644
--- a/src/mlpack/methods/lars/lars_main.cpp
+++ b/src/mlpack/methods/lars/lars_main.cpp
@@ -21,10 +21,16 @@ using namespace mlpack;
 using namespace mlpack::regression;
 using namespace mlpack::util;
 
-PROGRAM_INFO("LARS", "An implementation of LARS: Least Angle Regression "
-    "(Stagewise/laSso).  This is a stage-wise homotopy-based algorithm for "
-    "L1-regularized linear regression (LASSO) and L1+L2-regularized linear "
-    "regression (Elastic Net)."
+PROGRAM_INFO("LARS",
+    // Short description.
+    "An implementation of Least Angle Regression (Stagewise/laSso), also known"
+    " as LARS.  This can train a LARS/LASSO/Elastic Net model and use that "
+    "model or a pre-trained model to output regression predictions for a test "
+    "set.",
+    // Long description.
+    "An implementation of LARS: Least Angle Regression (Stagewise/laSso).  "
+    "This is a stage-wise homotopy-based algorithm for L1-regularized linear "
+    "regression (LASSO) and L1+L2-regularized linear regression (Elastic Net)."
     "\n\n"
     "This program is able to train a LARS/LASSO/Elastic Net model or load a "
     "model from file, output regression predictions for a test set, and save "
@@ -80,7 +86,12 @@ PROGRAM_INFO("LARS", "An implementation of LARS: Least Angle Regression "
     "and save those responses to " + PRINT_DATASET("test_predictions") + ": "
     "\n\n" +
     PRINT_CALL("lars", "input_model", "lasso_model", "test", "test",
-        "output_predictions", "test_predictions"));
+        "output_predictions", "test_predictions"),
+    SEE_ALSO("@linear_regression", "#linear_regression"),
+    SEE_ALSO("Least angle regression (pdf)",
+        "http://mlpack.org/papers/lars.pdf"),
+    SEE_ALSO("mlpack::regression::LARS C++ class documentation",
+        "@doxygen/classmlpack_1_1regression_1_1LARS.html"));
 
 PARAM_TMATRIX_IN("input", "Matrix of covariates (X).", "i");
 PARAM_MATRIX_IN("responses", "Matrix of responses/observations (y).", "r");
diff --git a/src/mlpack/methods/linear_regression/CMakeLists.txt b/src/mlpack/methods/linear_regression/CMakeLists.txt
index 58a7426e8..b4e63cdca 100644
--- a/src/mlpack/methods/linear_regression/CMakeLists.txt
+++ b/src/mlpack/methods/linear_regression/CMakeLists.txt
@@ -17,3 +17,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(linear_regression)
 add_python_binding(linear_regression)
+add_markdown_docs(linear_regression "cli;python" "regression")
diff --git a/src/mlpack/methods/linear_regression/linear_regression_main.cpp b/src/mlpack/methods/linear_regression/linear_regression_main.cpp
index c1e33cd06..1cb58a85c 100644
--- a/src/mlpack/methods/linear_regression/linear_regression_main.cpp
+++ b/src/mlpack/methods/linear_regression/linear_regression_main.cpp
@@ -22,6 +22,12 @@ using namespace arma;
 using namespace std;
 
 PROGRAM_INFO("Simple Linear Regression and Prediction",
+    // Short description.
+    "An implementation of simple linear regression and ridge regression using "
+    "ordinary least squares.  Given a dataset and responses, a model can be "
+    "trained and saved for later use, or a pre-trained model can be used to "
+    "output regression predictions for a test set.",
+    // Long description.
     "An implementation of simple linear regression and simple ridge regression "
     "using ordinary least squares. This solves the problem"
     "\n\n"
@@ -63,7 +69,13 @@ PROGRAM_INFO("Simple Linear Regression and Prediction",
     "used:"
     "\n\n" +
     PRINT_CALL("linear_regression", "input_model", "lr_model", "test", "X_test",
-        "output_predictions", "X_test_responses"));
+        "output_predictions", "X_test_responses"),
+    SEE_ALSO("Linear/ridge regression tutorial", "@doxygen/lrtutorial.html"),
+    SEE_ALSO("@lars", "#lars"),
+    SEE_ALSO("Linear regression on Wikipedia",
+        "https://en.wikipedia.org/wiki/Linear_regression"),
+    SEE_ALSO("mlpack::regression::LinearRegression C++ class documentation",
+        "@doxygen/classmlpack_1_1regression_1_1LinearRegression.html"));
 
 PARAM_MATRIX_IN("training", "Matrix containing training set X (regressors).",
     "t");
diff --git a/src/mlpack/methods/local_coordinate_coding/CMakeLists.txt b/src/mlpack/methods/local_coordinate_coding/CMakeLists.txt
index 6720525c7..d4b498e55 100644
--- a/src/mlpack/methods/local_coordinate_coding/CMakeLists.txt
+++ b/src/mlpack/methods/local_coordinate_coding/CMakeLists.txt
@@ -20,3 +20,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(local_coordinate_coding)
 add_python_binding(local_coordinate_coding)
+add_markdown_docs(local_coordinate_coding "cli;python" "transformations")
diff --git a/src/mlpack/methods/local_coordinate_coding/local_coordinate_coding_main.cpp b/src/mlpack/methods/local_coordinate_coding/local_coordinate_coding_main.cpp
index e0b8c2964..0925382bc 100644
--- a/src/mlpack/methods/local_coordinate_coding/local_coordinate_coding_main.cpp
+++ b/src/mlpack/methods/local_coordinate_coding/local_coordinate_coding_main.cpp
@@ -24,6 +24,12 @@ using namespace mlpack::sparse_coding; // For NothingInitializer.
 using namespace mlpack::util;
 
 PROGRAM_INFO("Local Coordinate Coding",
+    // Short description.
+    "An implementation of Local Coordinate Coding (LCC), a data transformation "
+    "technique.  Given input data, this transforms each point to be expressed "
+    "as a linear combination of a few points in the dataset; once an LCC model "
+    "is trained, it can be used to transform points later also.",
+    // Long description.
     "An implementation of Local Coordinate Coding (LCC), which "
     "codes data that approximately lives on a manifold using a variation of l1-"
     "norm regularized sparse coding.  Given a dense data matrix X with n points"
@@ -67,7 +73,13 @@ PROGRAM_INFO("Local Coordinate Coding",
     "be used:"
     "\n\n" +
     PRINT_CALL("local_coordinate_coding", "input_model", "lcc_model", "test",
-        "points", "codes", "new_codes"));
+        "points", "codes", "new_codes"),
+    SEE_ALSO("@sparse_coding", "#sparse_coding"),
+    SEE_ALSO("Nonlinear learning using local coordinate coding (pdf)",
+        "https://papers.nips.cc/paper/3875-nonlinear-learning-using-local-"
+        "coordinate-coding.pdf"),
+    SEE_ALSO("mlpack::lcc::LocalCoordinateCoding C++ class documentation",
+        "@doxygen/classmlpack_1_1lcc_1_1LocalCoordinateCoding.html"));
 
 // Training parameters.
 PARAM_MATRIX_IN("training", "Matrix of training data (X).", "t");
diff --git a/src/mlpack/methods/logistic_regression/CMakeLists.txt b/src/mlpack/methods/logistic_regression/CMakeLists.txt
index d7edd6ba1..673e214c6 100644
--- a/src/mlpack/methods/logistic_regression/CMakeLists.txt
+++ b/src/mlpack/methods/logistic_regression/CMakeLists.txt
@@ -19,3 +19,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(logistic_regression)
 add_python_binding(logistic_regression)
+add_markdown_docs(logistic_regression "cli;python" "classification")
diff --git a/src/mlpack/methods/logistic_regression/logistic_regression_main.cpp b/src/mlpack/methods/logistic_regression/logistic_regression_main.cpp
index 2a0ed23a7..385525ab0 100644
--- a/src/mlpack/methods/logistic_regression/logistic_regression_main.cpp
+++ b/src/mlpack/methods/logistic_regression/logistic_regression_main.cpp
@@ -24,6 +24,11 @@ using namespace mlpack::optimization;
 using namespace mlpack::util;
 
 PROGRAM_INFO("L2-regularized Logistic Regression and Prediction",
+    // Short description.
+    "An implementation of L2-regularized logistic regression for two-class "
+    "classification.  Given labeled data, a model can be trained and saved for "
+    "future use; or, a pre-trained model can be used to classify new points.",
+    // Long description.
     "An implementation of L2-regularized logistic regression using either the "
     "L-BFGS optimizer or SGD (stochastic gradient descent).  This solves the "
     "regression problem"
@@ -39,8 +44,8 @@ PROGRAM_INFO("L2-regularized Logistic Regression and Prediction",
     "those things at once.  In addition, this program allows classification on "
     "a test dataset (specified with the " + PRINT_PARAM_STRING("test") + " "
     "parameter) and the classification results may be saved with the " +
-    PRINT_PARAM_STRING("output") + " output parameter.  The trained logistic "
-    "regression model may be saved using the " +
+    PRINT_PARAM_STRING("output") + " output parameter."
+    " The trained logistic regression model may be saved using the " +
     PRINT_PARAM_STRING("output_model") + " output parameter."
     "\n\n"
     "The training data, if specified, may have class labels as its last "
@@ -95,7 +100,13 @@ PROGRAM_INFO("L2-regularized Logistic Regression and Prediction",
     PRINT_DATASET("predictions") + "', the following command may be used: "
     "\n\n" +
     PRINT_CALL("logistic_regression", "input_model", "lr_model", "test", "test",
-        "output", "predictions"));
+        "output", "predictions"),
+    SEE_ALSO("@softmax_regression", "#softmax_regression"),
+    SEE_ALSO("@random_forest", "#random_forest"),
+    SEE_ALSO("Logistic regression on Wikipedia",
+        "https://en.wikipedia.org/wiki/Logistic_regression"),
+    SEE_ALSO("mlpack::regression::LogisticRegression C++ class documentation",
+        "@doxygen/classmlpack_1_1regression_1_1LogisticRegression.html"));
 
 // Training parameters.
 PARAM_MATRIX_IN("training", "A matrix containing the training set (the matrix "
diff --git a/src/mlpack/methods/lsh/CMakeLists.txt b/src/mlpack/methods/lsh/CMakeLists.txt
index 758a2152b..4ec7baacf 100644
--- a/src/mlpack/methods/lsh/CMakeLists.txt
+++ b/src/mlpack/methods/lsh/CMakeLists.txt
@@ -19,3 +19,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 # sets with p-stable LSH.
 add_cli_executable(lsh)
 add_python_binding(lsh)
+add_markdown_docs(lsh "cli;python" "geometry")
diff --git a/src/mlpack/methods/lsh/lsh_main.cpp b/src/mlpack/methods/lsh/lsh_main.cpp
index d9923e02b..c082e10aa 100644
--- a/src/mlpack/methods/lsh/lsh_main.cpp
+++ b/src/mlpack/methods/lsh/lsh_main.cpp
@@ -25,6 +25,13 @@ using namespace mlpack::util;
 
 // Information about the program itself.
 PROGRAM_INFO("K-Approximate-Nearest-Neighbor Search with LSH",
+    // Short description.
+    "An implementation of approximate k-nearest-neighbor search with "
+    "locality-sensitive hashing (LSH).  Given a set of reference points and a "
+    "set of query points, this will compute the k approximate nearest neighbors"
+    " of each query point in the reference set; models can be saved for future "
+    "use.",
+    // Long description.
     "This program will calculate the k approximate-nearest-neighbors of a set "
     "of points using locality-sensitive hashing. You may specify a separate set"
     " of reference points and query points, or just a reference set which will "
@@ -49,7 +56,15 @@ PROGRAM_INFO("K-Approximate-Nearest-Neighbor Search with LSH",
     " parameter can be specified to set the random seed."
     "\n\n"
     "This program also has many other parameters to control its functionality;"
-    " see the parameter-specific documentation for more information.");
+    " see the parameter-specific documentation for more information.",
+    SEE_ALSO("@knn", "#knn"),
+    SEE_ALSO("@krann", "#krann"),
+    SEE_ALSO("Locality-sensitive hashing on Wikipedia",
+        "https://en.wikipedia.org/wiki/Locality-sensitive_hashing"),
+    SEE_ALSO("Locality-sensitive hashing scheme based on p-stable distributions"
+        " (pdf)", "http://mlpack.org/papers/lsh.pdf"),
+    SEE_ALSO("mlpack::neighbor::LSHSearch C++ class documentation",
+        "@doxygen/classmlpack_1_1neighbor_1_1LSHSearch.html"));
 
 // Define our input parameters that this program will take.
 PARAM_MATRIX_IN("reference", "Matrix containing the reference dataset.", "r");
diff --git a/src/mlpack/methods/mean_shift/CMakeLists.txt b/src/mlpack/methods/mean_shift/CMakeLists.txt
index 1087ab532..5aced5225 100644
--- a/src/mlpack/methods/mean_shift/CMakeLists.txt
+++ b/src/mlpack/methods/mean_shift/CMakeLists.txt
@@ -16,3 +16,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(mean_shift)
 add_python_binding(mean_shift)
+add_markdown_docs(mean_shift "cli;python" "clustering")
diff --git a/src/mlpack/methods/mean_shift/mean_shift_main.cpp b/src/mlpack/methods/mean_shift/mean_shift_main.cpp
index 4710ccaad..ac7f17e18 100644
--- a/src/mlpack/methods/mean_shift/mean_shift_main.cpp
+++ b/src/mlpack/methods/mean_shift/mean_shift_main.cpp
@@ -23,9 +23,15 @@ using namespace mlpack::util;
 using namespace std;
 
 // Define parameters for the executable.
-PROGRAM_INFO("Mean Shift Clustering", "This program performs mean shift "
-    "clustering on the given dataset, storing the learned cluster assignments "
-    "either as a column of labels in the input dataset or separately."
+PROGRAM_INFO("Mean Shift Clustering",
+    // Short description.
+    "A fast implementation of mean-shift clustering using dual-tree range "
+    "search.  Given a dataset, this uses the mean shift algorithm to produce "
+    "and return a clustering of the data.",
+    // Long description.
+    "This program performs mean shift clustering on the given dataset, storing "
+    "the learned cluster assignments either as a column of labels in the input "
+    "dataset or separately."
     "\n\n"
     "The input dataset should be specified with the " +
     PRINT_PARAM_STRING("input") + " parameter, and the radius used for search"
@@ -42,7 +48,16 @@ PROGRAM_INFO("Mean Shift Clustering", "This program performs mean shift "
     PRINT_DATASET("data") + " and store the centroids to " +
     PRINT_DATASET("centroids") + ", the following command may be used: "
     "\n\n" +
-    PRINT_CALL("mean_shift", "input", "data", "centroid", "centroids"));
+    PRINT_CALL("mean_shift", "input", "data", "centroid", "centroids"),
+    SEE_ALSO("@kmeans", "#kmeans"),
+    SEE_ALSO("@dbscan", "#dbscan"),
+    SEE_ALSO("Mean shift on Wikipedia",
+        "https://en.wikipedia.org/wiki/Mean_shift"),
+    SEE_ALSO("Mean Shift, Mode Seeking, and Clustering (pdf)",
+        "http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.510.1222"
+        "&rep=rep1&type=pdf"),
+    SEE_ALSO("mlpack::mean_shift::MeanShift C++ class documentation",
+        "@doxygen/classmlpack_1_1meanshift_1_1MeanShift.html"));
 
 // Required options.
 PARAM_MATRIX_IN_REQ("input", "Input dataset to perform clustering on.", "i");
diff --git a/src/mlpack/methods/naive_bayes/CMakeLists.txt b/src/mlpack/methods/naive_bayes/CMakeLists.txt
index f15dc229c..b2afe0707 100644
--- a/src/mlpack/methods/naive_bayes/CMakeLists.txt
+++ b/src/mlpack/methods/naive_bayes/CMakeLists.txt
@@ -16,3 +16,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(nbc)
 add_python_binding(nbc)
+add_markdown_docs(nbc "cli;python" "classification")
diff --git a/src/mlpack/methods/naive_bayes/nbc_main.cpp b/src/mlpack/methods/naive_bayes/nbc_main.cpp
index 68a4a70e0..612d675d0 100644
--- a/src/mlpack/methods/naive_bayes/nbc_main.cpp
+++ b/src/mlpack/methods/naive_bayes/nbc_main.cpp
@@ -26,6 +26,11 @@ using namespace std;
 using namespace arma;
 
 PROGRAM_INFO("Parametric Naive Bayes Classifier",
+    // Short description.
+    "An implementation of the Naive Bayes Classifier, used for classification. "
+    "Given labeled data, an NBC model can be trained and saved, or, a "
+    "pre-trained model can be used for classification.",
+    // Long description.
     "This program trains the Naive Bayes classifier on the given labeled "
     "training set, or loads a model from the given model file, and then may use"
     " that trained model to classify the points in a given test set."
@@ -66,7 +71,14 @@ PROGRAM_INFO("Parametric Naive Bayes Classifier",
     "may be used:"
     "\n\n" +
     PRINT_CALL("nbc", "input_model", "nbc_model", "test", "test_set", "output",
-        "predictions"));
+        "predictions"),
+    SEE_ALSO("@softmax_regression", "#softmax_regression"),
+    SEE_ALSO("@random_forest", "#random_forest"),
+    SEE_ALSO("Naive Bayes classifier on Wikipedia",
+        "https://en.wikipedia.org/wiki/Naive_Bayes_classifier"),
+    SEE_ALSO("mlpack::naive_bayes::NaiveBayesClassifier C++ class "
+        "documentation", "@doxygen/classmlpack_1_1naive__bayes_1_1"
+        "NaiveBayesClassifier.html"));
 
 // A struct for saving the model with mappings.
 struct NBCModel
diff --git a/src/mlpack/methods/nca/CMakeLists.txt b/src/mlpack/methods/nca/CMakeLists.txt
index 777eef39b..7b1fd98f5 100644
--- a/src/mlpack/methods/nca/CMakeLists.txt
+++ b/src/mlpack/methods/nca/CMakeLists.txt
@@ -18,3 +18,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(nca)
 add_python_binding(nca)
+add_markdown_docs(nca "cli;python" "transformations")
diff --git a/src/mlpack/methods/nca/nca_main.cpp b/src/mlpack/methods/nca/nca_main.cpp
index b1dd431ae..e26cae5e7 100644
--- a/src/mlpack/methods/nca/nca_main.cpp
+++ b/src/mlpack/methods/nca/nca_main.cpp
@@ -22,6 +22,12 @@
 
 // Define parameters.
 PROGRAM_INFO("Neighborhood Components Analysis (NCA)",
+    // Short description.
+    "An implementation of neighborhood components analysis, a distance learning"
+    " technique that can be used for preprocessing.  Given a labeled dataset, "
+    "this uses NCA, which seeks to improve the k-nearest-neighbor "
+    "classification, and returns the learned distance metric.",
+    // Long description.
     "This program implements Neighborhood Components Analysis, both a linear "
     "dimensionality reduction technique and a distance learning technique.  The"
     " method seeks to improve k-nearest-neighbor classification on a dataset "
@@ -82,7 +88,14 @@ PROGRAM_INFO("Neighborhood Components Analysis (NCA)",
     "mlpack L-BFGS documentation (in lbfgs.hpp) or the vast set of published "
     "literature on L-BFGS."
     "\n\n"
-    "By default, the SGD optimizer is used.");
+    "By default, the SGD optimizer is used.",
+    SEE_ALSO("Neighbourhood components analysis on Wikipedia",
+        "https://en.wikipedia.org/wiki/Neighbourhood_components_analysis"),
+    SEE_ALSO("Neighbourhood components analysis (pdf)",
+        "http://papers.nips.cc/paper/2566-neighbourhood-components-"
+        "analysis.pdf"),
+    SEE_ALSO("mlpack::nca::NCA C++ class documentation",
+        "@doxygen/classmlpack_1_1nca_1_1NCA.html"));
 
 PARAM_MATRIX_IN_REQ("input", "Input dataset to run NCA on.", "i");
 PARAM_MATRIX_OUT("output", "Output matrix for learned distance matrix.", "o");
diff --git a/src/mlpack/methods/neighbor_search/CMakeLists.txt b/src/mlpack/methods/neighbor_search/CMakeLists.txt
index 1958ebe17..789beb295 100644
--- a/src/mlpack/methods/neighbor_search/CMakeLists.txt
+++ b/src/mlpack/methods/neighbor_search/CMakeLists.txt
@@ -29,5 +29,8 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 # Add mlpack_knn and mlpack_kfn executables.
 add_cli_executable(knn)
 add_python_binding(knn)
+add_markdown_docs(knn "cli;python" "geometry")
+
 add_cli_executable(kfn)
 add_python_binding(kfn)
+add_markdown_docs(kfn "cli;python" "geometry")
diff --git a/src/mlpack/methods/neighbor_search/kfn_main.cpp b/src/mlpack/methods/neighbor_search/kfn_main.cpp
index 56415c3ae..57e2ffdf6 100644
--- a/src/mlpack/methods/neighbor_search/kfn_main.cpp
+++ b/src/mlpack/methods/neighbor_search/kfn_main.cpp
@@ -34,6 +34,12 @@ typedef NSModel<FurthestNeighborSort> KFNModel;
 
 // Information about the program itself.
 PROGRAM_INFO("k-Furthest-Neighbors Search",
+    // Short description.
+    "An implementation of k-furthest-neighbor search using single-tree and "
+    "dual-tree algorithms.  Given a set of reference points and query points, "
+    "this can find the k furthest neighbors in the reference set of each query"
+    " point using trees; trees that are built can be saved for future use.",
+    // Long description.
     "This program will calculate the k-furthest-neighbors of a set of "
     "points. You may specify a separate set of reference points and query "
     "points, or just a reference set which will be used as both the reference "
@@ -51,7 +57,13 @@ PROGRAM_INFO("k-Furthest-Neighbors Search",
     "neighbors output matrix corresponds to the index of the point in the "
     "reference set which is the j'th furthest neighbor from the point in the "
     "query set with index i.  Row i and column j in the distances output file "
-    "corresponds to the distance between those two points.");
+    "corresponds to the distance between those two points.",
+    SEE_ALSO("@approx_kfn", "#approx_kfn"),
+    SEE_ALSO("@knn", "#knn"),
+    SEE_ALSO("Tree-independent dual-tree algorithms (pdf)",
+        "http://proceedings.mlr.press/v28/curtin13.pdf"),
+    SEE_ALSO("mlpack::neighbor::NeighborSearch C++ class documentation",
+        "@doxygen/classmlpack_1_1neighbor_1_1NeighborSearch.html"));
 
 // Define our input parameters that this program will take.
 PARAM_MATRIX_IN("reference", "Matrix containing the reference dataset.", "r");
diff --git a/src/mlpack/methods/neighbor_search/knn_main.cpp b/src/mlpack/methods/neighbor_search/knn_main.cpp
index 336315dea..46a2b53bf 100644
--- a/src/mlpack/methods/neighbor_search/knn_main.cpp
+++ b/src/mlpack/methods/neighbor_search/knn_main.cpp
@@ -36,6 +36,12 @@ typedef NSModel<NearestNeighborSort> KNNModel;
 
 // Information about the program itself.
 PROGRAM_INFO("k-Nearest-Neighbors Search",
+    // Short description.
+    "An implementation of k-nearest-neighbor search using single-tree and "
+    "dual-tree algorithms.  Given a set of reference points and query points, "
+    "this can find the k nearest neighbors in the reference set of each query "
+    "point using trees; trees that are built can be saved for future use.",
+    // Long description.
     "This program will calculate the k-nearest-neighbors of a set of "
     "points using kd-trees or cover trees (cover tree support is experimental "
     "and may be slow). You may specify a separate set of "
@@ -53,7 +59,16 @@ PROGRAM_INFO("k-Nearest-Neighbors Search",
     "neighbors output matrix corresponds to the index of the point in the "
     "reference set which is the j'th nearest neighbor from the point in the "
     "query set with index i.  Row j and column i in the distances output matrix"
-    " corresponds to the distance between those two points.");
+    " corresponds to the distance between those two points.",
+    SEE_ALSO("@lsh", "#lsh"),
+    SEE_ALSO("@krann", "#krann"),
+    SEE_ALSO("@kfn", "#kfn"),
+    SEE_ALSO("NeighborSearch tutorial (k-nearest-neighbors)",
+        "@doxygen/nstutorial.html"),
+    SEE_ALSO("Tree-independent dual-tree algorithms (pdf)",
+        "http://proceedings.mlr.press/v28/curtin13.pdf"),
+    SEE_ALSO("mlpack::neighbor::NeighborSearch C++ class documentation",
+        "@doxygen/classmlpack_1_1neighbor_1_1NeighborSearch.html"));
 
 // Define our input parameters that this program will take.
 PARAM_MATRIX_IN("reference", "Matrix containing the reference dataset.", "r");
diff --git a/src/mlpack/methods/nmf/CMakeLists.txt b/src/mlpack/methods/nmf/CMakeLists.txt
index 18e01f1b4..f1c4666f3 100644
--- a/src/mlpack/methods/nmf/CMakeLists.txt
+++ b/src/mlpack/methods/nmf/CMakeLists.txt
@@ -1,2 +1,3 @@
 add_cli_executable(nmf)
 add_python_binding(nmf)
+add_markdown_docs(nmf "cli;python" "misc. / other")
diff --git a/src/mlpack/methods/nmf/nmf_main.cpp b/src/mlpack/methods/nmf/nmf_main.cpp
index e39273eb2..1e9e4a539 100644
--- a/src/mlpack/methods/nmf/nmf_main.cpp
+++ b/src/mlpack/methods/nmf/nmf_main.cpp
@@ -28,10 +28,15 @@ using namespace mlpack::util;
 using namespace std;
 
 // Document program.
-PROGRAM_INFO("Non-negative Matrix Factorization", "This program performs "
-    "non-negative matrix factorization on the given dataset, storing the "
-    "resulting decomposed matrices in the specified files.  For an input "
-    "dataset V, NMF decomposes V into two matrices W and H such that "
+PROGRAM_INFO("Non-negative Matrix Factorization",
+    // Short description.
+    "An implementation of non-negative matrix factorization.  This can be used "
+    "to decompose an input dataset into two low-rank non-negative components.",
+    // Long description.
+    "This program performs non-negative matrix factorization on the given "
+    "dataset, storing the resulting decomposed matrices in the specified "
+    "files.  For an input dataset V, NMF decomposes V into two matrices W "
+    "and H such that "
     "\n\n"
     "V = W * H"
     "\n\n"
@@ -60,7 +65,17 @@ PROGRAM_INFO("Non-negative Matrix Factorization", "This program performs "
     PRINT_DATASET("H") + ", the following command could be used: "
     "\n\n" +
     PRINT_CALL("nmf", "input", "V", "w", "W", "h", "H", "rank", 10,
-        "update_rules", "multdist"));
+        "update_rules", "multdist"),
+    SEE_ALSO("@cf", "#cf"),
+    SEE_ALSO("Alternating matrix factorization tutorial",
+        "@doxygen/amftutorial.html"),
+    SEE_ALSO("Non-negative matrix factorization on Wikipedia",
+        "https://en.wikipedia.org/wiki/Non-negative_matrix_factorization"),
+    SEE_ALSO("Algorithms for non-negative matrix factorization (pdf)",
+        "http://papers.nips.cc/paper/1861-algorithms-for-non-negative-matrix-"
+        "factorization.pdf"),
+    SEE_ALSO("mlpack::amf::AMF C++ class documentation",
+        "@doxygen/classmlpack_1_1amf_1_1AMF.html"));
 
 // Parameters for program.
 PARAM_MATRIX_IN_REQ("input", "Input dataset to perform NMF on.", "i");
diff --git a/src/mlpack/methods/pca/CMakeLists.txt b/src/mlpack/methods/pca/CMakeLists.txt
index 097598ee8..8b317c4fc 100644
--- a/src/mlpack/methods/pca/CMakeLists.txt
+++ b/src/mlpack/methods/pca/CMakeLists.txt
@@ -20,3 +20,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(pca)
 add_python_binding(pca)
+add_markdown_docs(pca "cli;python" "transformations")
diff --git a/src/mlpack/methods/pca/pca_main.cpp b/src/mlpack/methods/pca/pca_main.cpp
index 4176f0680..493a80026 100644
--- a/src/mlpack/methods/pca/pca_main.cpp
+++ b/src/mlpack/methods/pca/pca_main.cpp
@@ -26,12 +26,18 @@ using namespace mlpack::util;
 using namespace std;
 
 // Document program.
-PROGRAM_INFO("Principal Components Analysis", "This program performs principal "
-    "components analysis on the given dataset using the exact, randomized, "
-    "randomized block Krylov, or QUIC SVD method. It will transform the data "
-    "onto its principal components, optionally performing dimensionality "
-    "reduction by ignoring the principal components with the smallest "
-    "eigenvalues."
+PROGRAM_INFO("Principal Components Analysis",
+    // Short description.
+    "An implementation of several strategies for principal components analysis "
+    "(PCA), a common preprocessing step.  Given a dataset and a desired new "
+    "dimensionality, this can reduce the dimensionality of the data using the "
+    "linear transformation determined by PCA.",
+    // Long description.
+    "This program performs principal components analysis on the given dataset "
+    "using the exact, randomized, randomized block Krylov, or QUIC SVD method. "
+    "It will transform the data onto its principal components, optionally "
+    "performing dimensionality reduction by ignoring the principal components "
+    "with the smallest eigenvalues."
     "\n\n"
     "Use the " + PRINT_PARAM_STRING("input") + " parameter to specify the "
     "dataset to perform PCA on.  A desired new dimensionality can be specified "
@@ -52,7 +58,11 @@ PROGRAM_INFO("Principal Components Analysis", "This program performs principal "
     PRINT_DATASET("data_mod") + ", the following command can be used:"
     "\n\n" +
     PRINT_CALL("pca", "input", "data", "new_dimensionality", 5,
-        "decomposition_method", "randomized", "output", "data_mod"));
+        "decomposition_method", "randomized", "output", "data_mod"),
+    SEE_ALSO("Principal component analysis on Wikipedia",
+        "https://en.wikipedia.org/wiki/Principal_component_analysis"),
+    SEE_ALSO("mlpack::pca::PCA C++ class documentation",
+        "@doxygen/classmlpack_1_1pca_1_1PCA.html"));
 
 // Parameters for program.
 PARAM_MATRIX_IN_REQ("input", "Input dataset to perform PCA on.", "i");
diff --git a/src/mlpack/methods/perceptron/CMakeLists.txt b/src/mlpack/methods/perceptron/CMakeLists.txt
index f0b4afc59..96a003562 100644
--- a/src/mlpack/methods/perceptron/CMakeLists.txt
+++ b/src/mlpack/methods/perceptron/CMakeLists.txt
@@ -21,3 +21,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(perceptron)
 add_python_binding(perceptron)
+add_markdown_docs(perceptron "cli;python" "classification")
diff --git a/src/mlpack/methods/perceptron/perceptron_main.cpp b/src/mlpack/methods/perceptron/perceptron_main.cpp
index d23e6694b..5dde3939f 100644
--- a/src/mlpack/methods/perceptron/perceptron_main.cpp
+++ b/src/mlpack/methods/perceptron/perceptron_main.cpp
@@ -26,6 +26,12 @@ using namespace std;
 using namespace arma;
 
 PROGRAM_INFO("Perceptron",
+    // Short description.
+    "An implementation of a perceptron---a single level neural network--=for "
+    "classification.  Given labeled data, a perceptron can be trained and saved"
+    " for future use; or, a pre-trained perceptron can be used for "
+    "classification on new points.",
+    // Long description.
     "This program implements a perceptron, which is a single level neural "
     "network. The perceptron makes its predictions based on a linear predictor "
     "function combining a set of weights with the feature vector.  The "
@@ -75,7 +81,12 @@ PROGRAM_INFO("Perceptron",
     "cannot pass a perceptron model trained on 2 classes and then re-train with"
     " a 4-class dataset.  Similarly, attempting classification on a "
     "3-dimensional dataset with a perceptron that has been trained on 8 "
-    "dimensions will cause an error.");
+    "dimensions will cause an error.",
+    SEE_ALSO("@adaboost", "#adaboost"),
+    SEE_ALSO("Perceptron on Wikipedia",
+        "https://en.wikipedia.org/wiki/Perceptron"),
+    SEE_ALSO("mlpack::perceptron::Perceptron C++ class documentation",
+        "@doxygen/classmlpack_1_1perceptron_1_1Perceptron.html"));
 
 // When we save a model, we must also save the class mappings.  So we use this
 // auxiliary structure to store both the perceptron and the mapping, and we'll
diff --git a/src/mlpack/methods/preprocess/CMakeLists.txt b/src/mlpack/methods/preprocess/CMakeLists.txt
index af9299c64..a65487981 100644
--- a/src/mlpack/methods/preprocess/CMakeLists.txt
+++ b/src/mlpack/methods/preprocess/CMakeLists.txt
@@ -15,10 +15,17 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 #add_cli_executable(preprocess_stats)
 add_cli_executable(preprocess_split)
 add_python_binding(preprocess_split)
+add_markdown_docs(preprocess_split "cli;python" "preprocessing")
+
 add_cli_executable(preprocess_binarize)
 add_python_binding(preprocess_binarize)
+add_markdown_docs(preprocess_binarize "cli;python" "preprocessing")
+
 add_cli_executable(preprocess_describe)
 add_python_binding(preprocess_describe)
+add_markdown_docs(preprocess_describe "cli;python" "preprocessing")
+
 #add_cli_executable(preprocess_scan)
 add_cli_executable(preprocess_imputer)
 #add_python_binding(preprocess_imputer)
+add_markdown_docs(preprocess_imputer "cli" "preprocessing")
diff --git a/src/mlpack/methods/preprocess/preprocess_binarize_main.cpp b/src/mlpack/methods/preprocess/preprocess_binarize_main.cpp
index 53e6fb056..9d69834af 100644
--- a/src/mlpack/methods/preprocess/preprocess_binarize_main.cpp
+++ b/src/mlpack/methods/preprocess/preprocess_binarize_main.cpp
@@ -14,7 +14,13 @@
 #include <mlpack/core/util/mlpack_main.hpp>
 #include <mlpack/core/data/binarize.hpp>
 
-PROGRAM_INFO("Binarize Data", "This utility takes a dataset and binarizes the "
+PROGRAM_INFO("Binarize Data",
+    // Short description.
+    "A utility to binarize a dataset.  Given a dataset, this utility converts "
+    "each value in the desired dimension(s) to 0 or 1; this can be a useful "
+    "preprocessing step.",
+    // Long description.
+    "This utility takes a dataset and binarizes the "
     "variables into either 0 or 1 given threshold. User can apply binarization "
     "on a dimension or the whole dataset.  The dimension to apply binarization "
     "to can be specified using the " + PRINT_PARAM_STRING("dimension") +
@@ -38,7 +44,10 @@ PROGRAM_INFO("Binarize Data", "This utility takes a dataset and binarizes the "
     PRINT_DATASET("X") + ",  we could instead run"
     "\n\n" +
     PRINT_CALL("preprocess_binarize", "input", "X", "threshold", 5.0,
-        "dimension", 0, "output", "Y"));
+        "dimension", 0, "output", "Y"),
+    SEE_ALSO("@preprocess_describe", "#preprocess_describe"),
+    SEE_ALSO("@preprocess_imputer", "#preprocess_imputer"),
+    SEE_ALSO("@preprocess_split", "#preprocess_split"));
 
 // Define parameters for data.
 PARAM_MATRIX_IN_REQ("input", "Input data matrix.", "i");
diff --git a/src/mlpack/methods/preprocess/preprocess_describe_main.cpp b/src/mlpack/methods/preprocess/preprocess_describe_main.cpp
index 744f826a5..43b6df7f5 100644
--- a/src/mlpack/methods/preprocess/preprocess_describe_main.cpp
+++ b/src/mlpack/methods/preprocess/preprocess_describe_main.cpp
@@ -22,12 +22,17 @@ using namespace mlpack::util;
 using namespace std;
 using namespace boost;
 
-PROGRAM_INFO("Descriptive Statistics", "This utility takes a dataset and "
-    "prints out the descriptive statistics of the data. Descriptive statistics "
-    "is the discipline of quantitatively describing the main features of a "
-    "collection of information, or the quantitative description itself. The "
-    "program does not modify the original file, but instead prints out the "
-    "statistics to the console. The printed result will look like a table."
+PROGRAM_INFO("Descriptive Statistics",
+    // Short description.
+    "A utility for printing descriptive statistics about a dataset.  This "
+    "prints a number of details about a dataset in a tabular format.",
+    // Long description.
+    "This utility takes a dataset and prints out the descriptive statistics "
+    "of the data. Descriptive statistics is the discipline of quantitatively "
+    "describing the main features of a collection of information, or the "
+    "quantitative description itself. The program does not modify the original "
+    "file, but instead prints out the statistics to the console. The printed "
+    "result will look like a table."
     "\n\n"
     "Optionally, width and precision of the output can be adjusted by a user "
     "using the " + PRINT_PARAM_STRING("width") + " and " +
@@ -47,7 +52,10 @@ PROGRAM_INFO("Descriptive Statistics", "This utility takes a dataset and "
     "the dataset as a population, we could run"
     "\n\n" +
     PRINT_CALL("preprocess_describe", "input", "X", "width", 10, "precision", 5,
-        "verbose", true));
+        "verbose", true),
+    SEE_ALSO("@preprocess_binarize", "#preprocess_binarize"),
+    SEE_ALSO("@preprocess_imputer", "#preprocess_imputer"),
+    SEE_ALSO("@preprocess_split", "#preprocess_split"));
 
 // Define parameters for data.
 PARAM_MATRIX_IN_REQ("input", "Matrix containing data,", "i");
diff --git a/src/mlpack/methods/preprocess/preprocess_imputer_main.cpp b/src/mlpack/methods/preprocess/preprocess_imputer_main.cpp
index 848bbe9cf..1825334ad 100644
--- a/src/mlpack/methods/preprocess/preprocess_imputer_main.cpp
+++ b/src/mlpack/methods/preprocess/preprocess_imputer_main.cpp
@@ -23,8 +23,14 @@
 #include <mlpack/core/data/imputation_methods/custom_imputation.hpp>
 #include <mlpack/core/data/imputation_methods/listwise_deletion.hpp>
 
-PROGRAM_INFO("Impute Data", "This utility takes a dataset and converts user "
-    "defined missing variable to another to provide more meaningful analysis "
+PROGRAM_INFO("Impute Data",
+    // Short description.
+    "This utility provides several imputation strategies for missing data. "
+    "Given a dataset with missing values, this can impute according to several "
+    "strategies, including user-defined values.",
+    // Long description.
+    "This utility takes a dataset and converts a user-defined missing variable "
+    "to another to provide more meaningful analysis."
     "\n\n"
     "The program does not modify the original file, but instead makes a "
     "separate file to save the output data; You can save the output by "
@@ -35,7 +41,10 @@ PROGRAM_INFO("Impute Data", "This utility takes a dataset and converts user "
     "column-wise dataset, and save the result to result.csv, we could run"
     "\n\n"
     "$ mlpack_preprocess_imputer -i dataset.csv -o result.csv -m NULL -d 0 \n"
-    "> -s listwise_deletion");
+    "> -s listwise_deletion",
+    SEE_ALSO("@preprocess_binarize", "#preprocess_binarize"),
+    SEE_ALSO("@preprocess_describe", "#preprocess_describe"),
+    SEE_ALSO("@preprocess_split", "#preprocess_split"));
 
 PARAM_STRING_IN_REQ("input_file", "File containing data.", "i");
 PARAM_STRING_OUT("output_file", "File to save output into.", "o");
diff --git a/src/mlpack/methods/preprocess/preprocess_split_main.cpp b/src/mlpack/methods/preprocess/preprocess_split_main.cpp
index e789e0250..4b510b790 100644
--- a/src/mlpack/methods/preprocess/preprocess_split_main.cpp
+++ b/src/mlpack/methods/preprocess/preprocess_split_main.cpp
@@ -15,11 +15,16 @@
 #include <mlpack/core/util/cli.hpp>
 #include <mlpack/core/data/split_data.hpp>
 
-PROGRAM_INFO("Split Data", "This utility takes a dataset and optionally labels "
-    "and splits them into a training set and a test set. Before the split, the "
-    "points in the dataset are randomly reordered. The percentage of the "
-    "dataset to be used as the test set can be specified with the " +
-    PRINT_PARAM_STRING("test_ratio") + " parameter; the default is 0.2 (20%)."
+PROGRAM_INFO("Split Data",
+    // Short description.
+    "A utility to split data into a training and testing dataset.  This can "
+    "also split labels according to the same split.",
+    // Long description.
+    "This utility takes a dataset and optionally labels and splits them into a "
+    "training set and a test set. Before the split, the points in the dataset "
+    "are randomly reordered. The percentage of the dataset to be used as the "
+    "test set can be specified with the " + PRINT_PARAM_STRING("test_ratio") +
+    " parameter; the default is 0.2 (20%)."
     "\n\n"
     "The output training and test matrices may be saved with the " +
     PRINT_PARAM_STRING("training") + " and " + PRINT_PARAM_STRING("test") +
@@ -48,7 +53,10 @@ PROGRAM_INFO("Split Data", "This utility takes a dataset and optionally labels "
     "\n\n" +
     PRINT_CALL("preprocess_split", "input", "X", "input_labels", "y",
         "test_ratio", 0.3, "training", "X_train", "training_labels", "y_train",
-        "test", "X_test", "test_labels", "y_test"));
+        "test", "X_test", "test_labels", "y_test"),
+    SEE_ALSO("@preprocess_binarize", "#preprocess_binarize"),
+    SEE_ALSO("@preprocess_describe", "#preprocess_describe"),
+    SEE_ALSO("@preprocess_imputer", "#preprocess_imputer"));
 
 // Define parameters for data.
 PARAM_MATRIX_IN_REQ("input", "Matrix containing data.", "i");
diff --git a/src/mlpack/methods/radical/CMakeLists.txt b/src/mlpack/methods/radical/CMakeLists.txt
index 886566b11..c7dc29cac 100644
--- a/src/mlpack/methods/radical/CMakeLists.txt
+++ b/src/mlpack/methods/radical/CMakeLists.txt
@@ -15,3 +15,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(radical)
 add_python_binding(radical)
+add_markdown_docs(radical "cli;python" "transformations")
diff --git a/src/mlpack/methods/radical/radical_main.cpp b/src/mlpack/methods/radical/radical_main.cpp
index 129b3d7ac..7a2a51e36 100644
--- a/src/mlpack/methods/radical/radical_main.cpp
+++ b/src/mlpack/methods/radical/radical_main.cpp
@@ -15,11 +15,18 @@
 #include <mlpack/core/math/random.hpp>
 #include "radical.hpp"
 
-PROGRAM_INFO("RADICAL", "An implementation of RADICAL, a method for independent"
-    "component analysis (ICA).  Assuming that we have an input matrix X, the"
-    "goal is to find a square unmixing matrix W such that Y = W * X and the "
-    "dimensions of Y are independent components.  If the algorithm is running"
-    "particularly slowly, try reducing the number of replicates."
+PROGRAM_INFO("RADICAL",
+    // Short description.
+    "An implementation of RADICAL, a method for independent component analysis "
+    "(ICA).  Given a dataset, this can decompose the dataset into an unmixing "
+    "matrix and an independent component matrix; this can be useful for "
+    "preprocessing.",
+    // Long description.
+    "An implementation of RADICAL, a method for independent component analysis "
+    "(ICA).  Assuming that we have an input matrix X, the goal is to find a "
+    "square unmixing matrix W such that Y = W * X and the dimensions of Y are "
+    "independent components.  If the algorithm is running particularly slowly, "
+    "try reducing the number of replicates."
     "\n\n"
     "The input matrix to perform ICA on should be specified with the " +
     PRINT_PARAM_STRING("input") + " parameter.  The output matrix Y may be "
@@ -31,7 +38,14 @@ PROGRAM_INFO("RADICAL", "An implementation of RADICAL, a method for independent"
     "40 replicates, saving the independent components to " +
     PRINT_DATASET("ic") + ", the following command may be used: "
     "\n\n" +
-    PRINT_CALL("radical", "input", "X", "replicates", 40, "output_ic", "ic"));
+    PRINT_CALL("radical", "input", "X", "replicates", 40, "output_ic", "ic"),
+    SEE_ALSO("Independent component analysis on Wikipedia",
+        "https://en.wikipedia.org/wiki/Independent_component_analysis"),
+    SEE_ALSO("ICA using spacings estimates of entropy (pdf)",
+        "http://www.jmlr.org/papers/volume4/learned-miller03a/"
+        "learned-miller03a.pdf"),
+    SEE_ALSO("mlpack::radical::Radical C++ class documentation",
+        "@doxygen/classmlpack_1_1radical_1_1Radical.html"));
 
 PARAM_MATRIX_IN_REQ("input", "Input dataset for ICA.", "i");
 
diff --git a/src/mlpack/methods/random_forest/CMakeLists.txt b/src/mlpack/methods/random_forest/CMakeLists.txt
index 3ac70de8a..89b03837d 100644
--- a/src/mlpack/methods/random_forest/CMakeLists.txt
+++ b/src/mlpack/methods/random_forest/CMakeLists.txt
@@ -19,3 +19,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(random_forest)
 add_python_binding(random_forest)
+add_markdown_docs(random_forest "cli;python" "classification")
diff --git a/src/mlpack/methods/random_forest/random_forest_main.cpp b/src/mlpack/methods/random_forest/random_forest_main.cpp
index 2ca69b853..4915f1cd5 100644
--- a/src/mlpack/methods/random_forest/random_forest_main.cpp
+++ b/src/mlpack/methods/random_forest/random_forest_main.cpp
@@ -20,6 +20,12 @@ using namespace mlpack::util;
 using namespace std;
 
 PROGRAM_INFO("Random forests",
+    // Short description.
+    "An implementation of the standard random forest algorithm by Leo Breiman "
+    "for classification.  Given labeled data, a random forest can be trained "
+    "and saved for future use; or, a pre-trained random forest can be used for "
+    "classification.",
+    // Long description.
     "This program is an implementation of the standard random forest "
     "classification algorithm by Leo Breiman.  A random forest can be "
     "trained and saved for later use, or a random forest may be loaded "
@@ -70,7 +76,16 @@ PROGRAM_INFO("Random forests",
     "could call "
     "\n\n" +
     PRINT_CALL("random_forest", "input_model", "rf_model", "test", "test_set",
-        "test_labels", "test_labels", "predictions", "predictions"));
+        "test_labels", "test_labels", "predictions", "predictions"),
+    SEE_ALSO("@decision_tree", "#decision_tree"),
+    SEE_ALSO("@hoeffding_tree", "#hoeffding_tree"),
+    SEE_ALSO("@softmax_regression", "#softmax_regression"),
+    SEE_ALSO("Random forest on Wikipedia",
+        "https://en.wikipedia.org/wiki/Random_forest"),
+    SEE_ALSO("Random forests (pdf)",
+        "https://link.springer.com/content/pdf/10.1023/A:1010933404324.pdf"),
+    SEE_ALSO("mlpack::tree::RandomForest C++ class documentation",
+        "@doxygen/classmlpack_1_1tree_1_1RandomForest.html"));
 
 PARAM_MATRIX_IN("training", "Training dataset.", "t");
 PARAM_UROW_IN("labels", "Labels for training dataset.", "l");
diff --git a/src/mlpack/methods/range_search/CMakeLists.txt b/src/mlpack/methods/range_search/CMakeLists.txt
index 9d29343db..68a020315 100644
--- a/src/mlpack/methods/range_search/CMakeLists.txt
+++ b/src/mlpack/methods/range_search/CMakeLists.txt
@@ -21,3 +21,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(range_search)
 #add_python_binding(range_search)
+add_markdown_docs(range_search "cli" "geometry")
diff --git a/src/mlpack/methods/range_search/range_search_main.cpp b/src/mlpack/methods/range_search/range_search_main.cpp
index 27e49caf1..c50bb709b 100644
--- a/src/mlpack/methods/range_search/range_search_main.cpp
+++ b/src/mlpack/methods/range_search/range_search_main.cpp
@@ -29,6 +29,13 @@ using namespace mlpack::util;
 
 // Information about the program itself.
 PROGRAM_INFO("Range Search",
+    // Short description.
+    "An implementation of range search with single-tree and dual-tree "
+    "algorithms.  Given a set of reference points and a set of query points and"
+    " a range, this can find the set of reference points within the desired "
+    "range for each query point, and any trees built during the computation can"
+    " be saved for reuse with future range searches.",
+    // Long description.
     "This program implements range search with a Euclidean distance metric. "
     "For a given query point, a given range, and a given set of reference "
     "points, the program will return all of the reference points with distance "
@@ -55,7 +62,14 @@ PROGRAM_INFO("Range Search",
     " resultant CSV-like files may not be loadable by many programs.  However, "
     "at this time a better way to store this non-square result is not known.  "
     "As a result, any output files will be written as CSVs in this manner, "
-    "regardless of the given extension.");
+    "regardless of the given extension.",
+    SEE_ALSO("@knn", "#knn"),
+    SEE_ALSO("Range searching on Wikipedia",
+        "https://en.wikipedia.org/wiki/Range_searching"),
+    SEE_ALSO("Tree-independent dual-tree algorithms (pdf)",
+        "http://proceedings.mlr.press/v28/curtin13.pdf"),
+    SEE_ALSO("mlpack::range::RangeSearch C++ class documentation",
+        "@doxygen/classmlpack_1_1range_1_1RangeSearch.html"));
 
 // Define our input parameters that this program will take.
 PARAM_MATRIX_IN("reference", "Matrix containing the reference dataset.", "r");
diff --git a/src/mlpack/methods/rann/CMakeLists.txt b/src/mlpack/methods/rann/CMakeLists.txt
index 457a11dd6..3ba78994a 100644
--- a/src/mlpack/methods/rann/CMakeLists.txt
+++ b/src/mlpack/methods/rann/CMakeLists.txt
@@ -37,3 +37,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 # reference sets.
 add_cli_executable(krann)
 add_python_binding(krann)
+add_markdown_docs(krann "cli;python" "geometry")
diff --git a/src/mlpack/methods/rann/krann_main.cpp b/src/mlpack/methods/rann/krann_main.cpp
index 8224a2113..94661a53f 100644
--- a/src/mlpack/methods/rann/krann_main.cpp
+++ b/src/mlpack/methods/rann/krann_main.cpp
@@ -30,6 +30,13 @@ typedef RAModel<NearestNeighborSort> RANNModel;
 
 // Information about the program itself.
 PROGRAM_INFO("K-Rank-Approximate-Nearest-Neighbors (kRANN)",
+    // Short description.
+    "An implementation of rank-approximate k-nearest-neighbor search (kRANN) "
+    " using single-tree and dual-tree algorithms.  Given a set of reference "
+    "points and query points, this can find the k nearest neighbors in the "
+    "reference set of each query point using trees; trees that are built can "
+    "be saved for future use.",
+    // Long description.
     "This program will calculate the k rank-approximate-nearest-neighbors of a "
     "set of points. You may specify a separate set of reference points and "
     "query points, or just a reference set which will be used as both the "
@@ -55,7 +62,15 @@ PROGRAM_INFO("K-Rank-Approximate-Nearest-Neighbors (kRANN)",
     "neighbors output file corresponds to the index of the point in the "
     "reference set which is the i'th nearest neighbor from the point in the "
     "query set with index j.  Row i and column j in the distances output file "
-    "corresponds to the distance between those two points.");
+    "corresponds to the distance between those two points.",
+    SEE_ALSO("@knn", "#knn"),
+    SEE_ALSO("@lsh", "#lsh"),
+    SEE_ALSO("Rank-approximate nearest neighbor search: Retaining meaning and "
+        "speed in high dimensions (pdf)", "https://papers.nips.cc/paper/3864-"
+        "rank-approximate-nearest-neighbor-search-retaining-meaning-and-speed-"
+        "in-high-dimensions.pdf"),
+    SEE_ALSO("mlpack::neighbor::RASearch C++ class documentation",
+        "@doxygen/classmlpack_1_1neighbor_1_1RASearch.html"));
 
 // Define our input parameters that this program will take.
 PARAM_MATRIX_IN("reference", "Matrix containing the reference dataset.", "r");
diff --git a/src/mlpack/methods/softmax_regression/CMakeLists.txt b/src/mlpack/methods/softmax_regression/CMakeLists.txt
index c6770f7c4..60a7d4cff 100644
--- a/src/mlpack/methods/softmax_regression/CMakeLists.txt
+++ b/src/mlpack/methods/softmax_regression/CMakeLists.txt
@@ -19,3 +19,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(softmax_regression)
 add_python_binding(softmax_regression)
+add_markdown_docs(softmax_regression "cli;python" "classification")
diff --git a/src/mlpack/methods/softmax_regression/softmax_regression_main.cpp b/src/mlpack/methods/softmax_regression/softmax_regression_main.cpp
index 97b54a2a5..4e3f88e76 100644
--- a/src/mlpack/methods/softmax_regression/softmax_regression_main.cpp
+++ b/src/mlpack/methods/softmax_regression/softmax_regression_main.cpp
@@ -24,11 +24,18 @@ using namespace mlpack::regression;
 using namespace mlpack::util;
 
 // Define parameters for the executable.
-PROGRAM_INFO("Softmax Regression", "This program performs softmax regression, "
-    "a generalization of logistic regression to the multiclass case, and has "
-    "support for L2 regularization.  The program is able to train a model, load"
-    " an existing model, and give predictions (and optionally their accuracy) "
-    "for test data."
+PROGRAM_INFO("Softmax Regression",
+    // Short description.
+    "An implementation of softmax regression for classification, which is a "
+    "multiclass generalization of logistic regression.  Given labeled data, a "
+    "softmax regression model can be trained and saved for future use, or, a "
+    "pre-trained softmax regression model can be used for classification of "
+    "new points.",
+    // Long description.
+    "This program performs softmax regression, a generalization of logistic "
+    "regression to the multiclass case, and has support for L2 regularization. "
+    " The program is able to train a model, load  an existing model, and give "
+    "predictions (and optionally their accuracy) for test data."
     "\n\n"
     "Training a softmax regression model is done by giving a file of training "
     "points with the " + PRINT_PARAM_STRING("training") + " parameter and their"
@@ -71,7 +78,14 @@ PROGRAM_INFO("Softmax Regression", "This program performs softmax regression, "
     " " + PRINT_DATASET("predictions") + ", the following command can be used:"
     "\n\n" +
     PRINT_CALL("softmax_regression", "input_model", "sr_model", "test",
-        "test_points", "predictions", "predictions"));
+        "test_points", "predictions", "predictions"),
+    SEE_ALSO("@logistic_regression", "#logistic_regression"),
+    SEE_ALSO("@random_forest", "#random_forest"),
+    SEE_ALSO("Multinomial logistic regression (softmax regression) on "
+        "Wikipedia",
+        "https://en.wikipedia.org/wiki/Multinomial_logistic_regression"),
+    SEE_ALSO("mlpack::regression::SoftmaxRegression C++ class documentation",
+        "@doxygen/classmlpack_1_1regression_1_1SoftmaxRegression.html"));
 
 // Required options.
 PARAM_MATRIX_IN("training", "A matrix containing the training set (the matrix "
diff --git a/src/mlpack/methods/sparse_coding/CMakeLists.txt b/src/mlpack/methods/sparse_coding/CMakeLists.txt
index cfd1108cb..7bb0f7b79 100644
--- a/src/mlpack/methods/sparse_coding/CMakeLists.txt
+++ b/src/mlpack/methods/sparse_coding/CMakeLists.txt
@@ -20,3 +20,4 @@ set(MLPACK_SRCS ${MLPACK_SRCS} ${DIR_SRCS} PARENT_SCOPE)
 
 add_cli_executable(sparse_coding)
 add_python_binding(sparse_coding)
+add_markdown_docs(sparse_coding "cli;python" "transformations")
diff --git a/src/mlpack/methods/sparse_coding/sparse_coding_main.cpp b/src/mlpack/methods/sparse_coding/sparse_coding_main.cpp
index e4b561f53..515cb78eb 100644
--- a/src/mlpack/methods/sparse_coding/sparse_coding_main.cpp
+++ b/src/mlpack/methods/sparse_coding/sparse_coding_main.cpp
@@ -22,12 +22,20 @@ using namespace mlpack::math;
 using namespace mlpack::sparse_coding;
 using namespace mlpack::util;
 
-PROGRAM_INFO("Sparse Coding", "An implementation of Sparse Coding with "
-    "Dictionary Learning, which achieves sparsity via an l1-norm regularizer on"
-    " the codes (LASSO) or an (l1+l2)-norm regularizer on the codes (the "
-    "Elastic Net).  Given a dense data matrix X with d dimensions and n points,"
-    " sparse coding seeks to find a dense dictionary matrix D with k atoms in "
-    "d dimensions, and a sparse coding matrix Z with n points in k dimensions."
+PROGRAM_INFO("Sparse Coding",
+    // Short description.
+    "An implementation of Sparse Coding with Dictionary Learning.  Given a "
+    "dataset, this will decompose the dataset into a sparse combination of a "
+    "few dictionary elements, where the dictionary is learned during "
+    "computation; a dictionary can be reused for future sparse coding of new "
+    "points.",
+    // Long description.
+    "An implementation of Sparse Coding with Dictionary Learning, which "
+    "achieves sparsity via an l1-norm regularizer on the codes (LASSO) or an "
+    "(l1+l2)-norm regularizer on the codes (the Elastic Net).  Given a dense "
+    "data matrix X with d dimensions and n points, sparse coding seeks to find "
+    "a dense dictionary matrix D with k atoms in d dimensions, and a sparse "
+    "coding matrix Z with n points in k dimensions."
     "\n\n"
     "The original data matrix X can then be reconstructed as Z * D.  Therefore,"
     " this program finds a representation of each point in X as a sparse linear"
@@ -62,7 +70,18 @@ PROGRAM_INFO("Sparse Coding", "An implementation of Sparse Coding with "
     PRINT_DATASET("codes") + ": "
     "\n\n" +
     PRINT_CALL("sparse_coding", "input_model", "model", "test", "otherdata",
-        "codes", "codes"));
+        "codes", "codes"),
+    SEE_ALSO("@local_coordinate_coding", "#local_coordinate_coding"),
+    SEE_ALSO("Sparse dictionary learning on Wikipedia",
+        "https://en.wikipedia.org/wiki/Sparse_dictionary_learning"),
+    SEE_ALSO("Efficient sparse coding algorithms (pdf)",
+        "http://papers.nips.cc/paper/2979-efficient-sparse-coding-"
+        "algorithms.pdf"),
+    SEE_ALSO("Regularization and variable selection via the elastic net",
+        "http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.124.4696&"
+        "rep=rep1&type=pdf"),
+    SEE_ALSO("mlpack::sparse_coding::SparseCoding C++ class documentation",
+        "@doxygen/classmlpack_1_1sparse__coding_1_1SparseCoding.html"));
 
 // Train the model.
 PARAM_MATRIX_IN("training", "Matrix of training data (X).", "t");
-- 
2.20.1

