Newer
Older
from __future__ import (absolute_import, division, print_function)
from six import iteritems
import mantid
from .base import AlgorithmBaseDirective #pylint: disable=unused-import
from mantiddoc.tools.git_last_modified import get_file_last_modified_time
class SourceLinkError(Exception):
def __init__(self, value):
self.value = value
def __str__(self):
return str(self.value)
class SourceLinkDirective(AlgorithmBaseDirective):
"""
Obtains the github links to the .cpp and .h or .py files depending on the
name and version.
Example directive usage:
Default Usage:
.. sourcelink::
Overriding the filename for searching:
.. sourcelink::
:filename: SINQTranspose3D
suppressing sanity checks:
.. sourcelink::
:sanity_checks: 0
specifying specific files
(paths should use / and start from but not include the Mantid directory):
.. sourcelink::
:h: Framework/ICat/inc/MantidICat/CatalogSearch.h
:cpp: Framework/ICat/src/CatalogSearch.cpp
suppressing a specific file type (None is case insensitive):
.. sourcelink::
:filename: FilterEventsByLogValuePreNexus
:py: None
"""
required_arguments, optional_arguments = 0, 0
option_spec = {
"filename": str,
"sanity_checks": int,
"cpp": str,
"h": str,
"py": str
}
#IMPORTANT: keys must match the option spec above
# - apart from filename and sanity_checks
file_types = {
"h": "C++ header",
"cpp": "C++ source",
"py": "Python"
}
__source_root = None
def execute(self):
"""
Called by Sphinx when the ..sourcelink:: directive is encountered.
"""
file_paths = {}
error_string = ""
sanity_checks = self.options.get("sanity_checks", 1)
file_name = self.options.get("filename", None)
if file_name is None:
# build a sensible default
file_name = self.algorithm_name()
if (self.algorithm_version() != 1) and (self.algorithm_version() is not None):
file_name += str(self.algorithm_version())
for extension in self.file_types.keys():
file_paths[extension] = self.options.get(extension, None)
if file_paths[extension] is None:
try:
fname = self.find_source_file(file_name, extension)
file_paths[extension] = (
fname, get_file_last_modified_time(self.git_cache, self.source_root, fname)) \
if fname is not None else None
except SourceLinkError as err:
error_string += str(err) + "\n"
elif file_paths[extension].lower() == "none":
# the users has specifically chosen to suppress this - set it to a "proper" None
# but do not search for this file
file_paths[extension] = None
else:
# prepend the base framework directory
fname = os.path.join(self.source_root, file_paths[extension])
file_paths[extension] = (fname, get_file_last_modified_time(self.git_cache, self.source_root, fname))
if not os.path.exists(file_paths[extension][0]):
error_string += "Cannot find {} file at {}\n".format(
extension, file_paths[extension][0])
# throw accumulated errors now if you have any
if error_string != "":
raise SourceLinkError(error_string)
self.output_to_page(file_paths, file_name, sanity_checks)
return []
def find_source_file(self, file_name, extension):
"""
Searches the source code for a matching filename with the correct extension
"""
# parse the source tree if it has not already been done
if not self.file_lookup:
self.parse_source_tree()
try:
path_list = self.file_lookup[file_name][extension]
if len(path_list) == 1:
return path_list[0]
else:
suggested_path = "os_agnostic_path_to_file_from_source_root"
if len(path_list) > 1:
suggested_path = path_list[0].replace(self.source_root, "")
raise SourceLinkError("Found multiple possibilities for " +
file_name + "." + extension + "\n" +
"Possible matches" + str(path_list) +
"\n" +
"Specify one using the " + extension +
" option\n" +
"e.g. \n" +
".. sourcelink:\n" +
" :" + extension + ": " + suggested_path)
return self.file_lookup[file_name][extension]
except KeyError:
# value is not present
return None
env = self.state.document.settings.env
direc = env.srcdir #= C:\Mantid\Code\Mantid\docs\source
direc = os.path.join(direc, "..", "..") # assume root is two levels up
direc = os.path.abspath(direc)
self.__source_root = direc #pylint: disable=protected-access
def parse_source_tree(self):
"""
Fills the file_lookup dictionary after parsing the source code
env = self.state.document.settings.env
builddir = env.doctreedir # there should be a better setting option
builddir = os.path.join(builddir, "..", "..")
builddir = os.path.abspath(builddir)
for dir_name, _, file_list in os.walk(self.source_root):
if dir_name.startswith(builddir):
continue # don't check or add to the cache
for fname in file_list:
(base_name, file_extensions) = os.path.splitext(fname)
#strip the dot from the extension
file_extensions = file_extensions[1:]
#build the data object that is e.g.
#file_lookup["Rebin2"]["cpp"] = ['C:\Mantid\Code\Mantid\Framework\Algorithms\src\Rebin2.cpp','possible other location']
if file_extensions in self.file_types.keys():
if base_name not in self.file_lookup.keys():
self.file_lookup[base_name] = {}
if file_extensions not in self.file_lookup[base_name].keys():
self.file_lookup[base_name][file_extensions] = []
self.file_lookup[base_name][file_extensions].append(
os.path.join(dir_name, fname))
def output_to_page(self, file_paths, file_name, sanity_checks):
"""
Outputs the sourcelinks and heading to the rst page
and performs some sanity checks
"""
valid_ext_list = []
self.add_rst(self.make_header("Source"))
for extension, filepath in iteritems(file_paths):
if filepath is not None:
self.output_path_to_page(filepath, extension)
valid_ext_list.append(extension)
# do some sanity checks - unless suppressed
if sanity_checks > 0:
suggested_path = "os_agnostic_path_to_file_from_Code/Mantid"
if not valid_ext_list:
raise SourceLinkError("No file possibilities for " + file_name + " have been found\n" +
"Please specify a better one using the :filename: opiton or use the " +
str(list(self.file_types.keys())) + " options\n" +
" :" + list(self.file_types.keys())[0] + ": " + suggested_path + "\n "+
"or \n" +
".. sourcelink:\n" +
" :filename: " + file_name)
# if the have a cpp we should also have a h
if ("cpp" in valid_ext_list) ^ ("h" in valid_ext_list):
raise SourceLinkError("Only one of .h and .cpp found for " + file_name + "\n" +
"valid files found for " + str(valid_ext_list) + "\n" +
"Please specify the missing one using an " +
str(list(self.file_types.keys())) + " option\n" +
"e.g. \n" +
".. sourcelink:\n" +
" :" + list(self.file_types.keys())[0] + ": " + suggested_path)
def output_path_to_page(self, filepath, extension):
"""
Outputs the source link for a file to the rst page
"""
_, f_name = os.path.split(filepath[0])
self.add_rst("{}: `{} <{}>`_ *(last modified: {})*\n\n".format(
self.file_types[extension],
f_name,
self.convert_path_to_github_url(filepath[0]),
filepath[1]
))
def convert_path_to_github_url(self, file_path):
"""
Converts a file path to the github url for that same file
example path C:\Mantid\Code\Mantid/Framework/Algorithms/inc/MantidAlgorithms/MergeRuns.h
example url https://github.com/mantidproject/mantid/blob/master/Code/Mantid/Framework/Algorithms/inc/MantidAlgorithms/MergeRuns.h
"""
url = file_path
url = url.replace(self.source_root, "")
# harmonize slashes
url = url.replace("\\", "/")
# prepend the github part
url = "https://github.com/mantidproject/mantid/blob/" + mantid.kernel.revision_full() + url
return url
def setup(app):
"""
Setup the directives when the extension is activated
Args:
app: The main Sphinx application object
"""
app.add_directive('sourcelink', SourceLinkDirective)