From 36e8f20aa30e1aa8adb0caea01eee973145d5aac Mon Sep 17 00:00:00 2001
From: Jay Rainey <jawrainey@gmail.com>
Date: Fri, 23 May 2014 10:18:58 +0100
Subject: [PATCH] Add working set of Sphinx extensions.

Refs #9521
---
 .../docs/sphinxext/mantiddoc/__init__.py      |   0
 .../mantiddoc/directives/__init__.py          |   0
 .../mantiddoc/directives/algorithm.py         |  54 ++++++
 .../sphinxext/mantiddoc/directives/aliases.py |  32 ++++
 .../sphinxext/mantiddoc/directives/base.py    |  54 ++++++
 .../mantiddoc/directives/categories.py        |  42 +++++
 .../mantiddoc/directives/properties.py        | 159 ++++++++++++++++++
 .../sphinxext/mantiddoc/directives/summary.py |  32 ++++
 8 files changed, 373 insertions(+)
 create mode 100644 Code/Mantid/docs/sphinxext/mantiddoc/__init__.py
 create mode 100644 Code/Mantid/docs/sphinxext/mantiddoc/directives/__init__.py
 create mode 100644 Code/Mantid/docs/sphinxext/mantiddoc/directives/algorithm.py
 create mode 100644 Code/Mantid/docs/sphinxext/mantiddoc/directives/aliases.py
 create mode 100644 Code/Mantid/docs/sphinxext/mantiddoc/directives/base.py
 create mode 100644 Code/Mantid/docs/sphinxext/mantiddoc/directives/categories.py
 create mode 100644 Code/Mantid/docs/sphinxext/mantiddoc/directives/properties.py
 create mode 100644 Code/Mantid/docs/sphinxext/mantiddoc/directives/summary.py

diff --git a/Code/Mantid/docs/sphinxext/mantiddoc/__init__.py b/Code/Mantid/docs/sphinxext/mantiddoc/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/Code/Mantid/docs/sphinxext/mantiddoc/directives/__init__.py b/Code/Mantid/docs/sphinxext/mantiddoc/directives/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/Code/Mantid/docs/sphinxext/mantiddoc/directives/algorithm.py b/Code/Mantid/docs/sphinxext/mantiddoc/directives/algorithm.py
new file mode 100644
index 00000000000..a2bf960c594
--- /dev/null
+++ b/Code/Mantid/docs/sphinxext/mantiddoc/directives/algorithm.py
@@ -0,0 +1,54 @@
+from base import BaseDirective
+
+
+class AlgorithmDirective(BaseDirective):
+
+    """
+    Adds a referenceable link for a given algorithm, a title,
+    and a screenshot of the algorithm to an rst file.
+    """
+
+    required_arguments, optional_arguments = 1, 0
+
+    def run(self):
+        """
+        Called by Sphinx when the ..algorithm:: directive is encountered
+        """
+        algorithm_name = str(self.arguments[0])
+
+        # Seperate methods for each unique piece of functionality.
+        reference = self._make_reference_link(algorithm_name)
+        title = self._make_header(algorithm_name, True)
+        screenshot = self._get_algorithm_screenshot(algorithm_name)
+
+        return self._insert_rest(reference + title + screenshot)
+
+    def _make_reference_link(self, algorithm_name):
+        """
+        Outputs a reference to the top of the algorithm's rst file.
+
+        Args:
+          algorithm_name (str): The name of the algorithm to reference.
+
+        Returns:
+          str: A ReST formatted reference.
+        """
+        return ".. _" + algorithm_name.title() + ":" + "\n"
+
+    def _get_algorithm_screenshot(self, algorithm_name):
+        """
+        Obtains the location of the screenshot for a given algorithm.
+
+        Args:
+          algorithm_name (str): The name of the algorithm.
+
+        Returns:
+          str: The location of the screenshot for the given algorithm.
+        """
+        images_dir = self.state.document.settings.env.config["mantid_images"]
+        return ".. image:: " + images_dir + algorithm_name + ".png"
+
+
+def setup(app):
+    app.add_config_value('mantid_images', 'mantid_images', 'env')
+    app.add_directive('algorithm', AlgorithmDirective)
diff --git a/Code/Mantid/docs/sphinxext/mantiddoc/directives/aliases.py b/Code/Mantid/docs/sphinxext/mantiddoc/directives/aliases.py
new file mode 100644
index 00000000000..55895f5ead3
--- /dev/null
+++ b/Code/Mantid/docs/sphinxext/mantiddoc/directives/aliases.py
@@ -0,0 +1,32 @@
+from base import BaseDirective
+
+
+class AliasesDirective(BaseDirective):
+
+    """
+    Obtains the aliases for a given algorithm based on it's name.
+    """
+
+    required_arguments, optional_arguments = 1, 0
+
+    def run(self):
+        """
+        Called by Sphinx when the ..aliases:: directive is encountered.
+        """
+        title = self._make_header(__name__.title())
+        alias = self._get_alias(str(self.arguments[0]))
+        return self._insert_rest(title + alias)
+
+    def _get_alias(self, algorithm_name):
+        """
+        Return the alias for the named algorithm.
+
+        Args:
+          algorithm_name (str): The name of the algorithm to get the alias for.
+        """
+        alg = self._create_mantid_algorithm(algorithm_name)
+        return "This algorithm is also known as: " + "**" + alg.alias() + "**"
+
+
+def setup(app):
+    app.add_directive('aliases', AliasesDirective)
diff --git a/Code/Mantid/docs/sphinxext/mantiddoc/directives/base.py b/Code/Mantid/docs/sphinxext/mantiddoc/directives/base.py
new file mode 100644
index 00000000000..ac76ddcfeb6
--- /dev/null
+++ b/Code/Mantid/docs/sphinxext/mantiddoc/directives/base.py
@@ -0,0 +1,54 @@
+from docutils import statemachine
+from docutils.parsers.rst import Directive
+
+
+class BaseDirective(Directive):
+
+    """
+    Contains shared functionality for Mantid custom directives.
+    """
+
+    def _make_header(self, name, title=False):
+        """
+        Makes a ReStructuredText title from the algorithm's name.
+
+        Args:
+          algorithm_name (str): The name of the algorithm to use for the title.
+          title (bool): If True, line is inserted above & below algorithm name.
+
+        Returns:
+          str: ReST formatted header with algorithm_name as content.
+        """
+        line = "\n" + "-" * len(name) + "\n"
+        if title:
+            return line + name + line
+        else:
+            return name + line
+
+    def _insert_rest(self, text):
+        """
+        Inserts ReStructuredText into the algorithm file.
+
+        Args:
+          text (str): Inserts ReStructuredText into the algorithm file.
+
+        Returns:
+          list: Empty list. This is required by the inherited run method.
+        """
+        self.state_machine.insert_input(statemachine.string2lines(text), "")
+        return []
+
+    def _create_mantid_algorithm(self, algorithm_name):
+        """
+        Create and initializes a Mantid algorithm.
+
+        Args:
+          algorithm_name (str): The name of the algorithm to use for the title.
+
+        Returns:
+          algorithm: An instance of a Mantid algorithm.
+        """
+        from mantid.api import AlgorithmManager
+        alg = AlgorithmManager.createUnmanaged(algorithm_name)
+        alg.initialize()
+        return alg
diff --git a/Code/Mantid/docs/sphinxext/mantiddoc/directives/categories.py b/Code/Mantid/docs/sphinxext/mantiddoc/directives/categories.py
new file mode 100644
index 00000000000..c51a145b5f7
--- /dev/null
+++ b/Code/Mantid/docs/sphinxext/mantiddoc/directives/categories.py
@@ -0,0 +1,42 @@
+from base import BaseDirective
+
+
+class CategoriesDirective(BaseDirective):
+
+    """
+    Obtains the categories for a given algorithm based on it's name.
+    """
+
+    required_arguments, optional_arguments = 1, 0
+
+    def run(self):
+        """
+        Called by Sphinx when the ..categories:: directive is encountered.
+        """
+        title = self._make_header(__name__.title())
+        categories = self._get_categories(str(self.arguments[0]))
+        return self._insert_rest(title + categories)
+
+    def _get_categories(self, algorithm_name):
+        """
+        Return the categories for the named algorithm.
+
+        Args:
+          algorithm_name (str): The name of the algorithm.
+        """
+        alg = self._create_mantid_algorithm(algorithm_name)
+
+        # Create a list containing each category.
+        categories = alg.category().split("\\")
+
+        if len(categories) >= 2:
+            # Add a cross reference for each catagory.
+            links = (":ref:`%s` | " * len(categories)) % tuple(categories)
+            # Remove last three characters to remove last |
+            return ("`Categories: <categories.html>`_ " + links)[:-3]
+        else:
+            return "`Category: <categoies.html>`_ :ref:`%s`" % (categories)
+
+
+def setup(app):
+    app.add_directive('categories', CategoriesDirective)
diff --git a/Code/Mantid/docs/sphinxext/mantiddoc/directives/properties.py b/Code/Mantid/docs/sphinxext/mantiddoc/directives/properties.py
new file mode 100644
index 00000000000..64d5de49c74
--- /dev/null
+++ b/Code/Mantid/docs/sphinxext/mantiddoc/directives/properties.py
@@ -0,0 +1,159 @@
+from base import BaseDirective
+
+
+class PropertiesDirective(BaseDirective):
+
+    """
+    Outputs the given algorithm's properties into a ReST formatted table.
+    """
+    # Accept one required argument and no optional arguments.
+    required_arguments, optional_arguments = 1, 0
+
+    def run(self):
+        """
+        Called by Sphinx when the ..properties:: directive is encountered.
+        """
+        alg_name = str(self.arguments[0])
+        title = self._make_header(__name__.title())
+        properties_table = self._populate_properties_table(alg_name)
+        return self._insert_rest(title + properties_table)
+
+    def _populate_properties_table(self, algorithm_name):
+        """
+        Populates the ReST table with algorithm properties.
+
+        Args:
+          algorithm_name (str): The name of the algorithm.
+        """
+        alg = self._create_mantid_algorithm(algorithm_name)
+        alg_properties = alg.getProperties()
+
+        # Stores each property of the algorithm in a tuple.
+        properties = []
+
+        # Used to obtain the name for the direction property rather than an
+        # int.
+        direction_string = ["Input", "Output", "InOut", "None"]
+
+        for prop in alg_properties:
+            # Append a tuple of properties to the list.
+            properties.append((
+                str(prop.name),
+                str(direction_string[prop.direction]),
+                str(prop.type),
+                str(self._get_default_prop(prop)),
+                str(prop.documentation)
+            ))
+
+        # Build and add the properties to the ReST table.
+        return self._build_table(properties)
+
+    def _build_table(self, table_content):
+        """
+        Build the ReST format
+
+        Args:
+          table_content (list of tuples): Each tuple (row) container
+          property values for a unique property of that algorithm.
+
+        Returns:
+          str: ReST formatted table containing algorithm properties.
+        """
+        # Default values for the table headers.
+        header_content = (
+            'Name', 'Direction', 'Type', 'Default', 'Description')
+        # The width of the columns. Multiply row length by 10 to ensure small
+        # properties format correctly.
+        col_sizes = [max(len(row[i] * 10) for row in table_content)
+                     for i in range(len(header_content))]
+        # Use the column widths as a means to formatting columns.
+        formatter = ' '.join('{:<%d}' % col for col in col_sizes)
+        # Add whitespace to each column. This depends on the values returned by
+        # col_sizes.
+        table_content_formatted = [
+            formatter.format(*item) for item in table_content]
+        # Create a seperator for each column
+        seperator = formatter.format(*['=' * col for col in col_sizes])
+        # Build the table.
+        header = '\n' + seperator + '\n' + formatter.format(*header_content) + '\n'
+        content = seperator + '\n' + \
+            '\n'.join(table_content_formatted) + '\n' + seperator
+        # Join the header and footer.
+        return header + content
+
+    def _get_default_prop(self, prop):
+        """
+        Converts the default value of the property to a more use-friendly one.
+
+        Args:
+          prop (str): The algorithm property to use.
+
+        Returns:
+          str: The default value of the property.
+        """
+        from mantid.api import IWorkspaceProperty
+
+        # Used to obtain the name for the direction property rather than
+        # outputting an int.
+        direction_string = ["Input", "Output", "InOut", "None"]
+
+        # Nothing to show under the default section for an output properties
+        # that are not workspace properties.
+        if (direction_string[prop.direction] == "Output") and \
+           (not isinstance(prop, IWorkspaceProperty)):
+            default_prop = ""
+        elif (prop.isValid == ""):
+            default_prop = self._create_property_default_string(prop)
+        else:
+            default_prop = "Mandatory"
+        return default_prop
+
+    def _create_property_default_string(self, prop):
+        """
+        Converts the default value of the property to a more use-friendly one.
+
+        Args:
+          prop. The property to find the default value of.
+
+        Returns:
+          str: The string to add to the property table default section.
+        """
+
+        default = prop.getDefault
+        defaultstr = ""
+
+        # Convert to int, then float, then any string
+        try:
+            val = int(default)
+            if (val >= 2147483647):
+                defaultstr = "Optional"
+            else:
+                defaultstr = str(val)
+        except:
+            try:
+                val = float(default)
+                if (val >= 1e+307):
+                    defaultstr = "Optional"
+                else:
+                    defaultstr = str(val)
+            except:
+                # Fall-back default for anything
+                defaultstr = str(default)
+
+        # Replace the ugly default values with "Optional"
+        if (defaultstr == "8.9884656743115785e+307") or \
+           (defaultstr == "1.7976931348623157e+308") or \
+           (defaultstr == "2147483647"):
+            defaultstr = "Optional"
+
+        if str(prop.type) == "boolean":
+            if defaultstr == "1":
+                defaultstr = "True"
+            else:
+                defaultstr = "False"
+
+        return defaultstr
+
+
+def setup(app):
+    app.add_directive('properties', PropertiesDirective)
diff --git a/Code/Mantid/docs/sphinxext/mantiddoc/directives/summary.py b/Code/Mantid/docs/sphinxext/mantiddoc/directives/summary.py
new file mode 100644
index 00000000000..c66fc706a06
--- /dev/null
+++ b/Code/Mantid/docs/sphinxext/mantiddoc/directives/summary.py
@@ -0,0 +1,32 @@
+from base import BaseDirective
+
+
+class SummaryDirective(BaseDirective):
+
+    """
+    Obtains the summary for a given algorithm based on it's name.
+    """
+
+    required_arguments, optional_arguments = 1, 0
+
+    def run(self):
+        """
+        Called by Sphinx when the ..summary:: directive is encountered.
+        """
+        title = self._make_header(__name__.title())
+        summary = self._get_summary(str(self.arguments[0]))
+        return self._insert_rest(title + summary)
+
+    def _get_summary(self, algorithm_name):
+        """
+        Return the summary for the named algorithm.
+
+        Args:
+          algorithm_name (str): The name of the algorithm.
+        """
+        alg = self._create_mantid_algorithm(algorithm_name)
+        return alg.getWikiSummary()
+
+
+def setup(app):
+    app.add_directive('summary', SummaryDirective)
-- 
GitLab