From 2be01abe0e519936839930e9973f464c9b940104 Mon Sep 17 00:00:00 2001
From: Martyn Gigg <martyn.gigg@stfc.ac.uk>
Date: Wed, 13 Aug 2014 16:37:52 +0100
Subject: [PATCH] Allow runsphinx_*.py scripts to be called by anyone

The driver script for running the sphinx process can now be called by a
user without any special requirements. The main use case will be
running a doctest individually.

It should be noted that even restricting the run to a single file with the
new -R option will actually still cause Sphinx to read every source file
at least once. This is baked in to Sphinx and cannot be avoided. However,
the config pickling does mean that all source files are only read once and
then after this only changed files are reread.
Refs #9639
Refs #
---
 Code/Mantid/docs/runsphinx.py.in | 183 +++++++++++++++++++++++++------
 1 file changed, 149 insertions(+), 34 deletions(-)

diff --git a/Code/Mantid/docs/runsphinx.py.in b/Code/Mantid/docs/runsphinx.py.in
index b613a9095fa..6bffe814996 100644
--- a/Code/Mantid/docs/runsphinx.py.in
+++ b/Code/Mantid/docs/runsphinx.py.in
@@ -2,57 +2,172 @@
    module. This script calls the sphinx entry point with the necessary
    arguments
 """
+from optparse import OptionParser
 import os
+import re
 import sys
 
-#-----------------------------------------------------------------------------------------------
-def pathjoin(left, *args):
+DOC_EXT = ".rst"
+
+###############################################################################
+# CMake-populated variables
+###############################################################################
+BUILDER = "@BUILDER@"
+SRC_DIR = "@SPHINX_SRC_DIR@"
+CONF_DIR = "@SPHINX_CONF_DIR@"
+SPHINX_BUILD_DIR = "@SPHINX_BUILD_DIR@"
+SCREENSHOTS_DIR = "@SCREENSHOTS_DIR@"
+
+###############################################################################
+# Main
+###############################################################################
+
+def main(sysarg):
     """
-    Similar to os.path.join but just uses "/" as it's populated with CMake-style paths that are
-    always "/" separated, even on Windows.
+    Execute the Sphinx build.
+
+    Args:
+      sysarg (list): A list of strings giving arguments to the script,
+                     where it is assumed that the path to the script is the
+                     first argument
     """
-    return left + "/" + "/".join(args)
+    opts, args = parseargs(sysarg)
+    if len(args) > 0:
+        raise RuntimeError("Unexpected command line arguments: %s. "
+                           "Use -h for help" % ' '.join(args))
 
-#-----------------------------------------------------------------------------------------------
+    # Update sys path if we need to
+    update_path(opts.mantidpath)
 
-def main():
-    # Update path to find mantid
-    package_dir = "@CMAKE_LIBRARY_OUTPUT_DIRECTORY@"
-    runtime_config = os.environ.get("RUNTIME_CONFIG", "") # for visual studio and xcode
-    if runtime_config != "":
-        package_dir = pathjoin(package_dir, runtime_config)
-    sys.path.insert(0, package_dir)
-
-    # Update environment with screenshots path
-    screenshots_dir = "@SCREENSHOTS_DIR@"
-    if screenshots_dir != "":
-        os.environ["SCREENSHOTS_DIR"] = screenshots_dir
-
-    # All paths are cmake style so are "/" separated, even on Windows
-    builder = "@BUILDER@"
-    src_dir = "@SPHINX_SRC_DIR@"
-    conf_dir = "@SPHINX_CONF_DIR@"
-    sphinx_build_dir = "@SPHINX_BUILD_DIR@"
-    output_dir = pathjoin(sphinx_build_dir, builder)
-    doctree_dir = pathjoin(sphinx_build_dir, "doctrees")
+    # Find test files
+    testpaths = find_test_files(SRC_DIR, opts.testinclude)
+
+    # Update environment with screenshots path if necessary
+    if SCREENSHOTS_DIR != "":
+        os.environ["SCREENSHOTS_DIR"] = SCREENSHOTS_DIR
 
     # Arguments for main
+    output_dir = pathjoin(SPHINX_BUILD_DIR, BUILDER)
+    doctree_dir = pathjoin(SPHINX_BUILD_DIR, "doctrees")
     argv = [sys.executable,
-            "-b", builder,
+            "-b", BUILDER,
             "-d", doctree_dir,
-            "-c", conf_dir,
-            src_dir, output_dir]
+            "-c", CONF_DIR,
+            SRC_DIR, output_dir]
 
-    # See if we have been told to only process a particular file
-    src_file = os.environ.get("SPHINX_SRC_FILE", None)
-    if src_file is not None:
-        argv.append(src_file)
+    if testpaths is not None:
+        if len(testpaths) > 0:
+            argv.extend(testpaths)
+        else:
+            raise RuntimeError("No tests matched regex '%s'"
+                                % opts.testinclude)
 
     # Run
     import sphinx
     sys.exit(sphinx.main(argv))
 
+#-----------------------------------------------------------------------------------------------
+
+def parseargs(arglist):
+    """
+    Split script arguments into options and arguments.
+
+    Args:
+      arglist: List of strings to control program
+    """
+    parser = OptionParser(usage="Usage: %prog [options]",
+                          conflict_handler='error')
+    parser.add_option("-m", "--mantidpath", dest="mantidpath",
+                      help="Location of mantid package. Has no effect if run inside MantidPlot")
+    parser.add_option("-R", "--tests-regex", dest="testinclude",
+                      help="Regex specifying which tests to run. It is matched against the "
+                      "filename when considering whether to run a test.")
+    return parser.parse_args(arglist[1:]) # hack off filename
+
+#-----------------------------------------------------------------------------------------------
+
+def update_path(mantidpath):
+    """
+    If not inside MantidPlot (current check is whether we can import _qti)
+    then insert given path as first directory in sys.path
+
+    Args:
+      mantidpath (str): A string giving the location of the mantid module
+    """
+    try:
+        import _qti
+        gui = True
+    except ImportError:
+        gui = False
+
+    # If it's MantidPlot then we already know what our paths should be so ignore it
+    if gui:
+        return
+
+    # check for directory
+    if not os.path.isdir(os.path.join(mantidpath, "mantid")):
+        raise ValueError("Unable to find mantid package in '%s'" % mantidpath)
+    # is package valid
+    if not os.path.isfile(os.path.join(mantidpath, "mantid", "__init__.py")):
+        raise ValueError("Invalid mantid package. No __init__ found in '%s' %s")
+
+    # Update sys.path
+    sys.path.insert(0, mantidpath)
+
+#-----------------------------------------------------------------------------------------------
+
+def find_test_files(src_dir, name_re):
+    """
+    Find the test files that should be run based on a source directory
+    and regex.
+
+    Args:
+      src_dir (str): A string giving the source directory of doc files
+      name_re (str): A regex to match against a test filename.
+    Returns:
+      A list of paths to the chosen test files.
+    """
+    name_re_comp = re.compile(name_re)
+    testpaths = []
+    for dirpath, dirnames, filenames in os.walk(src_dir):
+        testfiles = find_matching_tests(filenames, name_re_comp)
+        # Join each filename with the current path and extend the list
+        testpaths.extend(map(lambda x: os.path.join(dirpath, x), testfiles))
+
+    return testpaths
+
+def find_matching_tests(filenames, name_re):
+    """
+    For a list of filenames, return the list that matches the given
+    regex
+
+    Args:
+      filenames: A list of filenames
+      name_re (re.regex): A compiled regex object
+    Returns:
+      A list of matching test names
+    """
+    testfiles = []
+    for filename in filenames:
+        if not filename.endswith(DOC_EXT):
+            continue
+        if name_re.match(filename.rstrip(DOC_EXT)):
+            testfiles.append(filename)
+
+    return testfiles
+
+#-----------------------------------------------------------------------------------------------
+def pathjoin(left, *args):
+    """
+    Similar to os.path.join but just uses "/" as it's populated with CMake-style paths that are
+    always "/" separated, even on Windows.
+    """
+    return left + "/" + "/".join(args)
+
+#-----------------------------------------------------------------------------------------------
+
+
 ##################################################################################
 
 if __name__ == "__main__":
-    main()
+    main(sys.argv)
-- 
GitLab