Skip to content

Commit

Permalink
split command linter
Browse files Browse the repository at this point in the history
  • Loading branch information
bernt-matthias committed Nov 26, 2023
1 parent 6a48b29 commit 8a16c62
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 68 deletions.
130 changes: 82 additions & 48 deletions lib/galaxy/tool_util/linters/command.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,88 @@
"""This module contains a linting function for a tool's command description.
"""This module contains linters for a tool's command description.
A command description describes how to build the command-line to execute
from supplied inputs.
"""
from typing import TYPE_CHECKING

from galaxy.tool_util.lint import Linter

def lint_command(tool_xml, lint_ctx):
"""Ensure tool contains exactly one command and check attributes."""
root = tool_xml.find("./command")
if root is None:
root = tool_xml.getroot()

commands = tool_xml.findall("./command")
if len(commands) > 1:
lint_ctx.error("More than one command tag found, behavior undefined.", node=commands[1])
return

if len(commands) == 0:
lint_ctx.error("No command tag found, must specify a command template to execute.", node=root)
return

command = get_command(tool_xml)
if command.text is None:
lint_ctx.error("Command is empty.", node=root)
elif "TODO" in command.text:
lint_ctx.warn("Command template contains TODO text.", node=command)

command_attrib = command.attrib
interpreter_type = None
for key, value in command_attrib.items():
if key == "interpreter":
interpreter_type = value
elif key == "detect_errors":
detect_errors = value
if detect_errors not in ["default", "exit_code", "aggressive"]:
lint_ctx.warn(f"Unknown detect_errors attribute [{detect_errors}]", node=command)

interpreter_info = ""
if interpreter_type:
interpreter_info = f" with interpreter of type [{interpreter_type}]"
if interpreter_type:
lint_ctx.warn("Command uses deprecated 'interpreter' attribute.", node=command)
lint_ctx.info(f"Tool contains a command{interpreter_info}.", node=command)


def get_command(tool_xml):
"""Get command XML element from supplied XML root."""
root = tool_xml.getroot()
commands = root.findall("command")
command = None
if len(commands) == 1:
command = commands[0]
return command
if TYPE_CHECKING:
from galaxy.tool_util.lint import LintContext
from galaxy.tool_util.parser.interface import ToolSource


class CommandMultiple(Linter):
@classmethod
def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"):
tool_xml = getattr(tool_source, "xml_tree", None)
if not tool_xml:
return
commands = tool_xml.findall("./command")
if len(commands) > 1:
lint_ctx.error("More than one command tag found, behavior undefined.", node=commands[1])


class CommandMissing(Linter):
@classmethod
def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"):
tool_xml = getattr(tool_source, "xml_tree", None)
if not tool_xml:
return
root = tool_xml.find("./command") or tool_xml.getroot()
command = tool_xml.find("./command")
if command is None:
lint_ctx.error("No command tag found, must specify a command template to execute.", node=root)


class CommandEmpty(Linter):
@classmethod
def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"):
tool_xml = getattr(tool_source, "xml_tree", None)
if not tool_xml:
return
root = tool_xml.find("./command") or tool_xml.getroot()
command = tool_xml.find("./command")
if command is not None and command.text is None:
lint_ctx.error("Command is empty.", node=root)


class CommandTODO(Linter):
@classmethod
def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"):
tool_xml = getattr(tool_source, "xml_tree", None)
if not tool_xml:
return
command = tool_xml.find("./command")
if command is not None and command.text is not None and "TODO" in command.text:
lint_ctx.warn("Command template contains TODO text.", node=command)


class CommandInterpreterDeprecated(Linter):
@classmethod
def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"):
tool_xml = getattr(tool_source, "xml_tree", None)
if not tool_xml:
return
command = tool_xml.find("./command")
if command is None:
return
interpreter_type = command.attrib.get("interpreter", None)
if interpreter_type is not None:
lint_ctx.warn("Command uses deprecated 'interpreter' attribute.", node=command)


class CommandInfo(Linter):
@classmethod
def lint(cls, tool_source: "ToolSource", lint_ctx: "LintContext"):
tool_xml = getattr(tool_source, "xml_tree", None)
if not tool_xml:
return
command = tool_xml.find("./command")
if command is None:
return
interpreter_type = command.attrib.get("interpreter", None)
interpreter_info = ""
if interpreter_type:
interpreter_info = f" with interpreter of type [{interpreter_type}]"
lint_ctx.info(f"Tool contains a command{interpreter_info}.", node=command)
44 changes: 24 additions & 20 deletions test/unit/tool_util/test_tool_linters.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@
# tests tool xml for command linter
COMMAND_MULTIPLE = """
<tool>
<command/>
<command/>
<command>ls</command>
<command>df</command>
</tool>
"""
COMMAND_MISSING = """
Expand Down Expand Up @@ -957,35 +957,39 @@ def test_citations_valid(lint_ctx):


def test_command_multiple(lint_ctx):
tool_xml_tree = get_xml_tree(COMMAND_MULTIPLE)
run_lint_module(lint_ctx, command, tool_xml_tree)
assert "More than one command tag found, behavior undefined." in lint_ctx.error_messages
assert len(lint_ctx.error_messages) == 1
assert not lint_ctx.info_messages
tool_source = get_xml_tool_source(COMMAND_MULTIPLE)
run_lint_module(lint_ctx, command, tool_source)
assert lint_ctx.error_messages == ["More than one command tag found, behavior undefined."]
assert lint_ctx.info_messages == ["Tool contains a command."]
assert not lint_ctx.valid_messages
assert not lint_ctx.warn_messages


def test_command_missing(lint_ctx):
tool_xml_tree = get_xml_tree(COMMAND_MISSING)
run_lint_module(lint_ctx, command, tool_xml_tree)
assert "No command tag found, must specify a command template to execute." in lint_ctx.error_messages
tool_source = get_xml_tool_source(COMMAND_MISSING)
run_lint_module(lint_ctx, command, tool_source)
assert lint_ctx.error_messages == ["No command tag found, must specify a command template to execute."]
assert not lint_ctx.info_messages
assert not lint_ctx.valid_messages
assert not lint_ctx.warn_messages


def test_command_todo(lint_ctx):
tool_xml_tree = get_xml_tree(COMMAND_TODO)
run_lint_module(lint_ctx, command, tool_xml_tree)
assert "Tool contains a command." in lint_ctx.info_messages
assert "Command template contains TODO text." in lint_ctx.warn_messages
tool_source = get_xml_tool_source(COMMAND_TODO)
run_lint_module(lint_ctx, command, tool_source)
assert not lint_ctx.error_messages
assert lint_ctx.info_messages == ["Tool contains a command."]
assert lint_ctx.warn_messages == ["Command template contains TODO text."]
assert not lint_ctx.valid_messages


def test_command_detect_errors_interpreter(lint_ctx):
tool_xml_tree = get_xml_tree(COMMAND_DETECT_ERRORS_INTERPRETER)
run_lint_module(lint_ctx, command, tool_xml_tree)
assert "Command uses deprecated 'interpreter' attribute." in lint_ctx.warn_messages
assert "Tool contains a command with interpreter of type [python]." in lint_ctx.info_messages
assert "Unknown detect_errors attribute [nonsense]" in lint_ctx.warn_messages
assert "Command is empty." in lint_ctx.error_messages
tool_source = get_xml_tool_source(COMMAND_DETECT_ERRORS_INTERPRETER)
run_lint_module(lint_ctx, command, tool_source)
assert lint_ctx.error_messages == ["Command is empty."]
assert lint_ctx.warn_messages == ["Command uses deprecated 'interpreter' attribute."]
assert lint_ctx.info_messages == ["Tool contains a command with interpreter of type [python]."]
assert not lint_ctx.valid_messages


def test_general_missing_tool_id_name_version(lint_ctx):
Expand Down

0 comments on commit 8a16c62

Please sign in to comment.