Newer
Older
from base import AlgorithmBaseDirective
import mantid
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"}
mantid_directory_cache=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:
file_paths[extension] = self.find_source_file(file_name,extension)
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
file_paths[extension] = os.path.join(self.get_mantid_directory(),file_paths[extension])
if not os.path.exists(file_paths[extension]):
error_string +="Cannot find " + extension + " file at " + file_paths[extension] + "\n"
#throw accumulated errors now if you have any
if error_string != "":
raise SourceLinkError(error_string)
try:
self.output_to_page(file_paths,file_name,sanity_checks);
except SourceLinkError as err:
error_string += str(err) + "\n"
if error_string != "":
raise SourceLinkError(error_string)
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 len(self.file_lookup) == 0:
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_Code/Mantid"
if len(path_list) > 1:
suggested_path = path_list[0]
#harmonize slashes
suggested_path = suggested_path.replace("\\","/")
#remove everything before and including the Mantid directory
strip_off_token = "Code/Mantid/"
index = suggested_path.find(strip_off_token)
if index != -1:
suggested_path = suggested_path[index+len(strip_off_token):]
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
def get_mantid_directory(self):
"""
returns the Code\Mantid directory
"""
if self.mantid_directory_cache is 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.mantid_directory_cache = direc
return self.mantid_directory_cache
def parse_source_tree(self):
"""
Fills the file_lookup dictionary after parsing the source code
for dirName, subdirList, fileList in os.walk(self.get_mantid_directory()):
for fname in fileList:
(baseName, fileExtension) = os.path.splitext(fname)
#strip the dot from the extension
fileExtension = fileExtension[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 fileExtension in self.file_types.keys():
if baseName not in self.file_lookup.keys():
self.file_lookup[baseName] = {}
if fileExtension not in self.file_lookup[baseName].keys():
self.file_lookup[baseName][fileExtension] = []
self.file_lookup[baseName][fileExtension].append(os.path.join(dirName,fname))
return
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 file_paths.iteritems():
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 len(valid_ext_list) == 0:
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(self.file_types.keys()) + " options\n" +
"e.g. \n" +
".. sourcelink:\n" +
" :" + 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) or ("h" in valid_ext_list):
if ("cpp" not in valid_ext_list) or ("h" not 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(self.file_types.keys()) + " option\n" +
"e.g. \n" +
".. sourcelink:\n" +
" :" + self.file_types.keys()[0] + ": " + suggested_path)
return
def output_path_to_page(self, filepath, extension):
"""
Outputs the source link for a file to the rst page
"""
dirName,fName = os.path.split(filepath)
self.add_rst(self.file_types[extension] + ": `" + fName + " <" + self.convert_path_to_github_url(filepath) + ">`_\n\n")
return
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
# remove the directory path
url = url.replace(self.get_mantid_directory(), "")
#harmonize slashes
url = url.replace("\\","/")
#prepend the github part
if not url.startswith("/"):
url = "/"+url
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)