diff --git a/docs/galaxy.tools.linters.rst b/docs/galaxy.tools.linters.rst index 05d4326d6db4e8c8bb3b932e5458139658d94046..2dd3a00996570262f82e7626e77406e9d6eca747 100644 --- a/docs/galaxy.tools.linters.rst +++ b/docs/galaxy.tools.linters.rst @@ -12,6 +12,14 @@ galaxy.tools.linters.citations module :undoc-members: :show-inheritance: +galaxy.tools.linters.command module +----------------------------------- + +.. automodule:: galaxy.tools.linters.command + :members: + :undoc-members: + :show-inheritance: + galaxy.tools.linters.help module -------------------------------- diff --git a/galaxy/tools/lint.py b/galaxy/tools/lint.py index 4682629cb159252205e719088c3f0aaf3220c3f0..895d0a6cbb63eb0278040b843d5462b405b131c9 100644 --- a/galaxy/tools/lint.py +++ b/galaxy/tools/lint.py @@ -31,6 +31,7 @@ class LintContext(object): self.found_warns = False def lint(self, module, name, lint_func, tool_xml): + name = name.replace("tsts", "tests") self.printed_linter_info = False self.valid_messages = [] self.info_messages = [] @@ -51,7 +52,7 @@ class LintContext(object): return self.printed_linter_info = True print("Applying linter %s... %s" % (name, status)) - + for message in self.error_messages: self.found_errors = True print_linter_info() diff --git a/galaxy/tools/linters/citations.py b/galaxy/tools/linters/citations.py index 1811563b3d2cd41ea1a0c7971934e7f3db43a1e7..2ff7e8c4cb14edf17b0614c257484a1a72485e5d 100644 --- a/galaxy/tools/linters/citations.py +++ b/galaxy/tools/linters/citations.py @@ -12,7 +12,7 @@ def lint_citations(tool_xml, lint_ctx): return valid_citations = 0 - for citation in citations.children(): + for citation in citations[0]: if citation.tag != "citation": lint_ctx.warn("Unknown tag discovered in citations block [%s], will be ignored." % citation.tag) if "type" in citation.attrib: diff --git a/galaxy/tools/linters/command.py b/galaxy/tools/linters/command.py new file mode 100644 index 0000000000000000000000000000000000000000..c2a355333dafbf4b156cc1392a11c3c81f84c20d --- /dev/null +++ b/galaxy/tools/linters/command.py @@ -0,0 +1,18 @@ + + +def lint_command(tool_xml, lint_ctx): + root = tool_xml.getroot() + commands = root.findall("command") + if len(commands) > 1: + lint_ctx.error("More than one command tag found, behavior undefined.") + return + + if len(commands) == 0: + lint_ctx.error("No command tag found, must specify a command template to execute.") + return + + command = commands[0] + if "TODO" in command: + lint_ctx.warn("Command template contains TODO text.") + + lint_ctx.info("Tool contains a command.") diff --git a/galaxy/tools/linters/help.py b/galaxy/tools/linters/help.py index 28ae675d114e45377510d707d738778dfb3ed2c9..ca13197e58b0718491b15080c987ae3945f1235d 100644 --- a/galaxy/tools/linters/help.py +++ b/galaxy/tools/linters/help.py @@ -1,3 +1,4 @@ +from galaxy.util import rst_to_html def lint_help(tool_xml, lint_ctx): @@ -11,5 +12,22 @@ def lint_help(tool_xml, lint_ctx): lint_ctx.warn("No help section found, consider adding a help section to your tool.") return - # TODO: validate help section RST. + help = helps[0].text + if not help.strip(): + lint_ctx.warn("Help section appears to be empty.") + return + lint_ctx.valid("Tool contains help section.") + invalid_rst = False + try: + rst_to_html(help) + except Exception as e: + invalid_rst = str(e) + + if "TODO" in help: + lint_ctx.warn("Help contains TODO text.") + + if invalid_rst: + lint_ctx.warn("Invalid reStructuredText found in help - [%s]." % invalid_rst) + else: + lint_ctx.valid("Help contains valid reStructuredText.") diff --git a/galaxy/tools/linters/outputs.py b/galaxy/tools/linters/outputs.py index 2891985043f2f4cdb49a8f7d891eb3922afd43f8..40e086cbaadcfaa76617f73aa778293e4dbb12fc 100644 --- a/galaxy/tools/linters/outputs.py +++ b/galaxy/tools/linters/outputs.py @@ -3,7 +3,7 @@ def lint_output(tool_xml, lint_ctx): outputs = tool_xml.findall("./outputs/data") if not outputs: - lint_ctx.warn("Tool contains no outputs, most tools should produce outputs..") + lint_ctx.warn("Tool contains no outputs, most tools should produce outputs.") return num_outputs = 0 @@ -15,7 +15,7 @@ def lint_output(tool_xml, lint_ctx): format_set = True format = output_attrib["format"] if format == "input": - lint_ctx.warn("Using format='input' on output data, format_source attribute is less ambigious and should be used instead.") + lint_ctx.warn("Using format='input' on output data, format_source attribute is less ambiguous and should be used instead.") elif "format_source" in output_attrib: format_set = True diff --git a/galaxy/tools/loader.py b/galaxy/tools/loader.py index a17a3a5fc6e93b54d955c4eef8f535c38808371a..42fc53105ba50e07c1dd8c4b43adfcf8141b2db7 100644 --- a/galaxy/tools/loader.py +++ b/galaxy/tools/loader.py @@ -120,6 +120,7 @@ def _expand_macro(element, expand_el, macros): # HACK for elementtree, newer implementations (etree/lxml) won't # require this parent_map data structure but elementtree does not # track parents or recongnize .find('..'). + # TODO fix this now that we're not using elementtree parent_map = dict((c, p) for p in element.getiterator() for c in p) _xml_replace(expand_el, macro_def, parent_map) diff --git a/galaxy/tools/loader_directory.py b/galaxy/tools/loader_directory.py index 95508318a24f9ab1f3b1285f71dd1ee3ca6dd90c..8b473a93ee7e79d51a2c39db76620298f33abd4f 100644 --- a/galaxy/tools/loader_directory.py +++ b/galaxy/tools/loader_directory.py @@ -2,21 +2,44 @@ import glob import os from ..tools import loader +import sys + +import logging +log = logging.getLogger(__name__) + PATH_DOES_NOT_EXIST_ERROR = "Could not load tools from path [%s] - this path does not exist." +LOAD_FAILURE_ERROR = "Failed to load tool with path %s." + +def load_exception_handler(path, exc_info): + log.warn(LOAD_FAILURE_ERROR % path, exc_info=exc_info) -def load_tool_elements_from_path(path): + +def load_tool_elements_from_path(path, load_exception_handler=load_exception_handler): tool_elements = [] for file in __find_tool_files(path): - if __looks_like_a_tool(file): - tool_elements.append((file, loader.load_tool(file))) + try: + looks_like_a_tool = __looks_like_a_tool(file) + except IOError: + # Some problem reading the tool file, skip. + continue + + if looks_like_a_tool: + try: + tool_elements.append((file, loader.load_tool(file))) + except Exception: + exc_info = sys.exc_info() + load_exception_handler(file, exc_info) return tool_elements def __looks_like_a_tool(path): with open(path) as f: for i in range(10): - line = f.next() + try: + line = f.next() + except StopIteration: + break if "<tool" in line: return True return False diff --git a/galaxy/util/__init__.py b/galaxy/util/__init__.py index 27e5e9c926b0dbebae5d80e8986ea9df4bb5892c..3469dca65e97330140f7792839c130d24606f4dc 100644 --- a/galaxy/util/__init__.py +++ b/galaxy/util/__init__.py @@ -10,13 +10,19 @@ try: import grp except ImportError: grp = None +try: + import docutils.core + import docutils.writers.html4css1 +except ImportError: + pass import errno import urlparse from tempfile import NamedTemporaryFile -from logging import getLogger -log = getLogger(__name__) +import logging +log = logging.getLogger(__name__) BUFFER_SIZE = 4096 +DEFAULT_ENCODING = os.environ.get('GALAXY_DEFAULT_ENCODING', 'utf-8') def enum(**enums): @@ -130,6 +136,8 @@ def xml_text(root, name=None): # asbool implementation pulled from PasteDeploy truthy = frozenset(['true', 'yes', 'on', 'y', 't', '1']) falsy = frozenset(['false', 'no', 'off', 'n', 'f', '0']) + + def asbool(obj): if isinstance(obj, basestring): obj = obj.strip().lower() @@ -196,3 +204,29 @@ def mask_password_from_url( url ): split = split._replace(netloc=split.netloc.replace("%s:%s" % (split.username, split.password), '%s:********' % split.username)) url = urlparse.urlunsplit(split) return url + + +def unicodify( value, encoding=DEFAULT_ENCODING, error='replace', default=None ): + """ + Returns a unicode string or None + """ + + if isinstance( value, unicode ): + return value + try: + return unicode( str( value ), encoding, error ) + except: + return default + + +def rst_to_html( s ): + """Convert a blob of reStructuredText to HTML""" + log = logging.getLogger( "docutils" ) + + class FakeStream( object ): + def write( self, str ): + if len( str ) > 0 and not str.isspace(): + log.warn( str ) + return unicodify( docutils.core.publish_string( s, + writer=docutils.writers.html4css1.Writer(), + settings_overrides={ "embed_stylesheet": False, "template": os.path.join(os.path.dirname(__file__), "docutils_template.txt"), "warning_stream": FakeStream() } ) ) diff --git a/galaxy/util/docutils_template.txt b/galaxy/util/docutils_template.txt new file mode 100644 index 0000000000000000000000000000000000000000..92aee1b5257ebd47e3b7392549284f520bd651f2 --- /dev/null +++ b/galaxy/util/docutils_template.txt @@ -0,0 +1 @@ +%(body)s