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