diff --git a/Code/Mantid/docs/sphinxext/mantiddoc/directives/algorithm.py b/Code/Mantid/docs/sphinxext/mantiddoc/directives/algorithm.py index 50382f9599782272e8f451b8f0c810a0aaa744b2..26726079b1b34ae202cea13b01111c687ef9fca2 100644 --- a/Code/Mantid/docs/sphinxext/mantiddoc/directives/algorithm.py +++ b/Code/Mantid/docs/sphinxext/mantiddoc/directives/algorithm.py @@ -1,6 +1,8 @@ from base import BaseDirective import os +REDIRECT_TEMPLATE = "redirect.html" + class AlgorithmDirective(BaseDirective): """ @@ -8,19 +10,19 @@ class AlgorithmDirective(BaseDirective): and a screenshot of the algorithm to an rst file. """ - required_arguments, optional_arguments = 1, 0 + required_arguments, optional_arguments = 0, 0 def run(self): """ Called by Sphinx when the ..algorithm:: directive is encountered """ - algorithm_name = str(self.arguments[0]) + algorithm_name, version = self._algorithm_name_and_version() # Seperate methods for each unique piece of functionality. reference = self._make_reference_link(algorithm_name) title = self._make_header(algorithm_name, True) toc = self._make_local_toc() - imgpath = self._create_screenshot(algorithm_name) + imgpath = self._create_screenshot(algorithm_name, version) screenshot = self._make_screenshot_link(algorithm_name, imgpath) return self._insert_rest(reference + title + screenshot + toc) @@ -36,20 +38,21 @@ class AlgorithmDirective(BaseDirective): Returns: str: A ReST formatted reference. """ - return ".. _%s:\n" % algorithm_name + return ".. _algorithm|%s:\n" % algorithm_name def _make_local_toc(self): return ".. contents:: Table of Contents\n :local:\n" - def _create_screenshot(self, algorithm_name): + def _create_screenshot(self, algorithm_name, version): """ Creates a screenshot for the named algorithm in an "images/screenshots" subdirectory of the currently processed document - The file will be named "algorithmname_dlg.png", e.g. Rebin_dlg.png + The file will be named "algorithmname-vX_dlg.png", e.g. Rebin-v1_dlg.png Args: algorithm_name (str): The name of the algorithm. + version (int): The version of the algorithm Returns: str: The full path to the created image @@ -62,7 +65,7 @@ class AlgorithmDirective(BaseDirective): os.makedirs(screenshots_dir) try: - imgpath = algorithm_screenshot(algorithm_name, screenshots_dir) + imgpath = algorithm_screenshot(algorithm_name, screenshots_dir, version=version) except Exception, exc: env.warn(env.docname, "Unable to generate screenshot for '%s' - %s" % (algorithm_name, str(exc))) imgpath = os.path.join(screenshots_dir, "failed_dialog.png") @@ -85,7 +88,7 @@ class AlgorithmDirective(BaseDirective): """ format_str = ".. figure:: %s\n"\ " :class: screenshot\n\n"\ - " %s\n" + " %s\n\n" filename = os.path.split(img_path)[1] path = "/images/screenshots/" + filename @@ -111,6 +114,18 @@ class AlgorithmDirective(BaseDirective): ############################################################################################################ +def html_collect_pages(app): + """ + Write out unversioned algorithm pages that redirect to the highest version of the algorithm + """ + name = "algorithms/Rebin" + context = {"name" : "Rebin", "target" : "Rebin-v1.html"} + template = REDIRECT_TEMPLATE + + return [(name, context, template)] + +############################################################################################################ + def setup(app): """ Setup the directives when the extension is activated @@ -119,3 +134,7 @@ def setup(app): app: The main Sphinx application object """ app.add_directive('algorithm', AlgorithmDirective) + + # connect event html collection to handler + app.connect("html-collect-pages", html_collect_pages) + diff --git a/Code/Mantid/docs/sphinxext/mantiddoc/directives/aliases.py b/Code/Mantid/docs/sphinxext/mantiddoc/directives/aliases.py index fed6897088221f7b53f51ce31ffd0a34c07c101e..494181945a7c39dd7339ed54294e95afa12f1836 100644 --- a/Code/Mantid/docs/sphinxext/mantiddoc/directives/aliases.py +++ b/Code/Mantid/docs/sphinxext/mantiddoc/directives/aliases.py @@ -7,24 +7,25 @@ class AliasesDirective(BaseDirective): Obtains the aliases for a given algorithm based on it's name. """ - required_arguments, optional_arguments = 1, 0 + required_arguments, optional_arguments = 0, 0 def run(self): """ Called by Sphinx when the ..aliases:: directive is encountered. """ title = self._make_header("Aliases") - alias = self._get_alias(str(self.arguments[0])) + alias = self._get_alias() return self._insert_rest(title + alias) - def _get_alias(self, algorithm_name): + def _get_alias(self): """ 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) + name, version = self._algorithm_name_and_version() + alg = self._create_mantid_algorithm(name, version) return "This algorithm is also known as: " + "**" + alg.alias() + "**" diff --git a/Code/Mantid/docs/sphinxext/mantiddoc/directives/base.py b/Code/Mantid/docs/sphinxext/mantiddoc/directives/base.py index d3e911ed8b7edd66cc3122aed29a456d1024d868..349b1fede17bfcaa9717f218bd39947d9a014380 100644 --- a/Code/Mantid/docs/sphinxext/mantiddoc/directives/base.py +++ b/Code/Mantid/docs/sphinxext/mantiddoc/directives/base.py @@ -1,6 +1,6 @@ from docutils import statemachine from docutils.parsers.rst import Directive - +import re class BaseDirective(Directive): @@ -11,21 +11,40 @@ class BaseDirective(Directive): has_content = True final_argument_whitespace = True - def _make_header(self, name, title=False): + alg_docname_re = re.compile(r'^([A-Z][a-zA-Z0-9]+)-v([0-9][0-9]*)$') + + def _algorithm_name_and_version(self): + """ + Returns the name and version of an algorithm based on the name of the + document. The expected name of the document is "AlgorithmName-v?", which + is the name of the file with the extension removed + """ + env = self.state.document.settings.env + # env.docname includes path, using forward slashes, from root of documentation directory + docname = env.docname.split("/")[-1] + match = self.alg_docname_re.match(docname) + if not match or len(match.groups()) != 2: + raise RuntimeError("Document filename '%s.rst' does not match the expected format: AlgorithmName-vX.rst" % docname) + + grps = match.groups() + return (str(grps[0]), int(grps[1])) + + def _make_header(self, name, pagetitle=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. + pagetitle (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: + if pagetitle: + line = "\n" + "=" * len(name) + "\n" return line + name + line else: + line = "\n" + "-" * len(name) + "\n" return name + line def _insert_rest(self, text): @@ -41,17 +60,18 @@ class BaseDirective(Directive): self.state_machine.insert_input(statemachine.string2lines(text), "") return [] - def _create_mantid_algorithm(self, algorithm_name): + def _create_mantid_algorithm(self, algorithm_name, version): """ Create and initializes a Mantid algorithm. Args: algorithm_name (str): The name of the algorithm to use for the title. + version (int): Version of the algorithm to create Returns: algorithm: An instance of a Mantid algorithm. """ from mantid.api import AlgorithmManager - alg = AlgorithmManager.createUnmanaged(algorithm_name) + alg = AlgorithmManager.createUnmanaged(algorithm_name, version) alg.initialize() return alg diff --git a/Code/Mantid/docs/sphinxext/mantiddoc/directives/categories.py b/Code/Mantid/docs/sphinxext/mantiddoc/directives/categories.py index 0a4d147a5e43f84bcec9448335ea4668cc0f366a..30deab430ec8a7d4533a260db54203a8ade32b73 100644 --- a/Code/Mantid/docs/sphinxext/mantiddoc/directives/categories.py +++ b/Code/Mantid/docs/sphinxext/mantiddoc/directives/categories.py @@ -1,47 +1,282 @@ +""" + Provides directives for dealing with category pages. + + While parsing the directives a list of the categories and associated pages/subcategories + is tracked. When the final set of html pages is collected, a processing function + creates "index" pages that lists the contents of each category. The display of each + "index" page is controlled by a jinja2 template. +""" from base import BaseDirective +CATEGORY_INDEX_TEMPLATE = "category.html" +# relative to the "root" directory +CATEGORIES_HTML_DIR = "categories" -class CategoriesDirective(BaseDirective): +class LinkItem(object): + """ + Defines a linkable item with a name and html reference + """ + # Name displayed on listing page + name = None + # html link + link = None + + def __init__(self, name, env): + """ + Arguments: + env (Sphinx.BuildEnvironment): The current environment processing object + """ + self.name = str(name) + + rel_path = env.docname # no suffix + # Assumes the link is for the current document and that the + # page that will use this reference is in a single + # subdirectory from root + self.link = "../%s.html" % rel_path + + def __eq__(self, other): + """ + Define comparison for two objects as the comparison of their names + + Arguments: + other (PageRef): Another PageRef object to compare + """ + return self.name == other.name + + def __hash__(self): + return hash(self.name) + + def __repr__(self): + return self.name + + def html_link(self): + """ + Returns a link for use as a href to refer to this document from a + categories page. It assumes that the category pages are in a subdirectory + of the root and that the item to be referenced is in the algorithms directory + under the root. + + Returns: + str: A string containing the link + """ + return self.link +# endclass +class PageRef(LinkItem): """ - Obtains the categories for a given algorithm based on it's name. + Store details of a single page reference """ - required_arguments, optional_arguments = 1, 0 + def __init__(self, name, env): + super(PageRef, self).__init__(name, env) + +#endclass + +class Category(LinkItem): + """ + Store information about a single category + """ + # Collection of PageRef objects that link to members of the category + pages = None + # Collection of PageRef objects that form subcategories of this category + subcategories = None + + def __init__(self, name, env): + super(Category, self).__init__(name, env) + + # override default link + self.link = "../categories/%s.html" % name + self.pages = set([]) + self.subcategories = set([]) + +#endclass + +class CategoriesDirective(BaseDirective): + """ + Records the page as part of the given categories. Index pages for each + category are then automatically created after all pages are collected + together. + + Subcategories can be given using the "\\" separator, e.g. Algorithms\\Transforms + """ + + # requires at least 1 category + required_arguments = 1 + # it can be in many categories and we put an arbitrary upper limit here + optional_arguments = 25 def run(self): """ - Called by Sphinx when the ..categories:: directive is encountered. + Called by Sphinx when the defined directive is encountered. """ - categories = self._get_categories(str(self.arguments[0])) - return self._insert_rest("\n" + categories) + categories = self._get_categories_list() + display_name = self._get_display_name() + links = self._create_links_and_track(display_name, categories) + + return self._insert_rest("\n" + links) - def _get_categories(self, algorithm_name): + def _get_categories_list(self): """ - Return the categories for the named algorithm. + Returns a list of the category strings + + Returns: + list: A list of strings containing the required categories + """ + # Simply return all of the arguments as strings + return self.arguments + + def _get_display_name(self): + """ + Returns the name of the item as it should appear in the category + """ + env = self.state.document.settings.env + # env.docname returns relative path from doc root. Use name after last "/" separator + return env.docname.split("/")[-1] + + def _create_links_and_track(self, page_name, category_list): + """ + Return the reST text required to link to the given + categories. As the categories are parsed they are + stored within the current environment for use in the + "html_collect_pages" function. Args: - algorithm_name (str): The name of the algorithm. + page_name (str): Name to use to refer to this page on the category index page + category_list (list): List of category strings + + Returns: + str: A string of reST that will define the links """ - alg = self._create_mantid_algorithm(algorithm_name) + env = self.state.document.settings.env + if not hasattr(env, "categories"): + env.categories = {} + + link_rst = "" + ncategs = 0 + for item in category_list: + if r"\\" in item: + categs = item.split(r"\\") + else: + categs = [item] + # endif - # Create a list containing each category. - categories = alg.category().split("\\") + parent = None + for index, categ_name in enumerate(categs): + if categ_name not in env.categories: + category = Category(categ_name, env) + env.categories[categ_name] = category + else: + category = env.categories[categ_name] + #endif - 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] + category.pages.add(PageRef(page_name, env)) + if index > 0: # first is never a child + parent.subcategories.add(Category(categ_name, env)) + #endif + + link_rst += "`%s <../%s/%s.html>`_ | " % (categ_name, CATEGORIES_HTML_DIR, categ_name) + ncategs += 1 + parent = category + # endfor + # endfor + + link_rst = "**%s**: " + link_rst.rstrip(" | ") # remove final separator + if ncategs == 1: + link_rst = link_rst % "Category" else: - return "`Category: <categoies.html>`_ :ref:`%s`" % (categories) + link_rst = link_rst % "Categories" + #endif + return link_rst + #end def -def setup(app): +#--------------------------------------------------------------------------------- + +class AlgorithmCategoryDirective(CategoriesDirective): """ - Setup the directives when the extension is activated + Supports the "algm_categories" directive that takes a single + argument and pulls the categories from an algorithm object. - Args: - app: The main Sphinx application object + In addition to adding the named page to the requested category, it + also appends it to the "Algorithms" category """ + # requires at least 1 argument + required_arguments = 0 + # no other arguments + optional_arguments = 0 + + def _get_categories_list(self): + """ + Returns a list of the category strings + + Returns: + list: A list of strings containing the required categories + """ + category_list = ["Algorithms"] + algname, version = self._algorithm_name_and_version() + alg_cats = self._create_mantid_algorithm(algname, version).categories() + for cat in alg_cats: + # double up the category separators so they are not treated as escape characters + category_list.append(cat.replace("\\", "\\\\")) + + return category_list + + def _get_display_name(self): + """ + Returns the name of the item as it should appear in the category + """ + return self._algorithm_name_and_version()[0] + +#--------------------------------------------------------------------------------- + +def html_collect_pages(app): + """ + Callback for the 'html-collect-pages' Sphinx event. Adds category + pages + a global Categories.html page that lists the pages included. + + Function returns an iterable (pagename, context, html_template), + where context is a dictionary defining the content that will fill the template + + Arguments: + app: A Sphinx application object + """ + if not hasattr(app.builder.env, "categories"): + return # nothing to do + + for name, context, template in create_category_pages(app): + yield (name, context, template) +# enddef + +def create_category_pages(app): + """ + Returns an iterable of (category_name, context, "category.html") + + Arguments: + app: A Sphinx application object + """ + env = app.builder.env + + # jinja2 html template + template = CATEGORY_INDEX_TEMPLATE + + categories = env.categories + for name, category in categories.iteritems(): + context = {} + context["title"] = category.name + # sort subcategories & pages by first letter + context["subcategories"] = sorted(category.subcategories, key = lambda x: x.name[0]) + context["pages"] = sorted(category.pages, key = lambda x: x.name[0]) + + yield (CATEGORIES_HTML_DIR + "/" + name, context, template) +# enddef + +#------------------------------------------------------------------------------ +def setup(app): + # Add categories directive app.add_directive('categories', CategoriesDirective) + # Add algm_categories directive + app.add_directive('algm_categories', AlgorithmCategoryDirective) + + # connect event html collection to handler + app.connect("html-collect-pages", html_collect_pages) + diff --git a/Code/Mantid/docs/sphinxext/mantiddoc/directives/properties.py b/Code/Mantid/docs/sphinxext/mantiddoc/directives/properties.py index 2e3b67a92e3cfe13943d5f24ddcc5efbec5a633a..440e11d4bd7ad943980f35c0004b02a10476e89c 100644 --- a/Code/Mantid/docs/sphinxext/mantiddoc/directives/properties.py +++ b/Code/Mantid/docs/sphinxext/mantiddoc/directives/properties.py @@ -7,25 +7,23 @@ 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 + required_arguments, optional_arguments = 0, 0 def run(self): """ Called by Sphinx when the ..properties:: directive is encountered. """ - alg_name = str(self.arguments[0]) title = self._make_header("Properties") - properties_table = self._populate_properties_table(alg_name) + properties_table = self._populate_properties_table() return self._insert_rest(title + properties_table) - def _populate_properties_table(self, algorithm_name): + def _populate_properties_table(self): """ Populates the ReST table with algorithm properties. - - Args: - algorithm_name (str): The name of the algorithm. """ - alg = self._create_mantid_algorithm(algorithm_name) + name, version = self._algorithm_name_and_version() + + alg = self._create_mantid_algorithm(name, version) alg_properties = alg.getProperties() # Stores each property of the algorithm in a tuple. diff --git a/Code/Mantid/docs/sphinxext/mantiddoc/directives/summary.py b/Code/Mantid/docs/sphinxext/mantiddoc/directives/summary.py index 22a8f73c695d4c20a16aa85220b0fba9818699c8..2432a2fc407608c8039f33cb3794ced32578d83b 100644 --- a/Code/Mantid/docs/sphinxext/mantiddoc/directives/summary.py +++ b/Code/Mantid/docs/sphinxext/mantiddoc/directives/summary.py @@ -7,24 +7,25 @@ class SummaryDirective(BaseDirective): Obtains the summary for a given algorithm based on it's name. """ - required_arguments, optional_arguments = 1, 0 + required_arguments, optional_arguments = 0, 0 def run(self): """ Called by Sphinx when the ..summary:: directive is encountered. """ title = self._make_header("Summary") - summary = self._get_summary(str(self.arguments[0])) + summary = self._get_summary() return self._insert_rest(title + summary) - def _get_summary(self, algorithm_name): + def _get_summary(self): """ Return the summary for the named algorithm. Args: algorithm_name (str): The name of the algorithm. """ - alg = self._create_mantid_algorithm(algorithm_name) + name, version = self._algorithm_name_and_version() + alg = self._create_mantid_algorithm(name, version) return alg.getWikiSummary() diff --git a/Code/Mantid/docs/sphinxext/mantiddoc/tools/screenshot.py b/Code/Mantid/docs/sphinxext/mantiddoc/tools/screenshot.py index 692517d62ec998c01474d30fba3f9024aef5c290..67dd69a477755a0a3b2877ed8e970019f3f23e89 100644 --- a/Code/Mantid/docs/sphinxext/mantiddoc/tools/screenshot.py +++ b/Code/Mantid/docs/sphinxext/mantiddoc/tools/screenshot.py @@ -11,7 +11,7 @@ ISIS Rutherford Appleton Laboratory & NScD Oak Ridge National Laboratory """ -def algorithm_screenshot(name, directory, ext=".png"): +def algorithm_screenshot(name, directory, version = -1, ext = ".png"): """ Takes a snapshot of an algorithm dialog and saves it as an image named "name_dlg.png" @@ -19,6 +19,7 @@ def algorithm_screenshot(name, directory, ext=".png"): Args: name (str): The name of the algorithm directory (str): An directory path where the image should be saved + version (str): A version of the algorithm to use (default=latest) ext (str): An optional extension (including the period). Default=.png Returns: @@ -29,9 +30,11 @@ def algorithm_screenshot(name, directory, ext=".png"): iface_mgr = mantidqt.MantidQt.API.InterfaceManager() # threadsafe_call required for MantidPlot - dlg = threadsafe_call(iface_mgr.createDialogFromName, name, True) + dlg = threadsafe_call(iface_mgr.createDialogFromName, name, True, None) + + suffix = ("-v%d" % version) if version != -1 else "" + filename = "%s%s_dlg%s" % (name, suffix, ext) - filename = name + "_dlg" + ext img_path = screenshot_to_dir(widget=dlg, filename=filename, screenshot_dir=directory) threadsafe_call(dlg.close)