From 1de2d73f38a5a97fc33f3c814ceecd490b299fe4 Mon Sep 17 00:00:00 2001 From: John Chilton Date: Tue, 16 Jun 2015 20:57:14 -0400 Subject: [PATCH] Allow parameterization of XML macros. This comes in two modes - simple lists of required attributes and per-attribute parameters that may specify defaults. The following toy example: ``` ``` will evalute to ````. The XML macro definition tag can now take in two new fixed attributes dollar_tokens and at_tokens - which will be interpreted as a list of parameter names. Each parameter can be specified when expanding the macros as simple attributes specifying the value of the parameter. The "dollar" parameter with name bar would map to a token with name $BAR$. Likewise an "at" parameter with name foo would map to a token with name @FOO@. Both parameter types can be specified on a per-parameter basis to also specify default values. So the following example: ``` @FOO@ ``` Would evalute to ``helloworldthe_default``. Updated unit tests explain the above much more succinctly - though some like English and words for reasons that escape me. --- lib/galaxy/tools/loader.py | 45 +++++++++++++++++++++++++---- test/unit/tools/test_tool_loader.py | 38 ++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 5 deletions(-) diff --git a/lib/galaxy/tools/loader.py b/lib/galaxy/tools/loader.py index 0daefa3ba5d..b0fbe1062ba 100644 --- a/lib/galaxy/tools/loader.py +++ b/lib/galaxy/tools/loader.py @@ -3,6 +3,8 @@ from xml.etree import ElementTree, ElementInclude from copy import deepcopy import os +REQUIRED_PARAMETER = object() + def load_tool(path): """ @@ -17,7 +19,7 @@ def load_tool(path): tokens = _macros_of_type(root, 'token', lambda el: el.text) # Expand xml macros - macro_dict = _macros_of_type(root, 'xml', lambda el: list(el)) + macro_dict = _macros_of_type(root, 'xml', lambda el: XmlMacroDef(el)) _expand_macros([root], macro_dict, tokens) return tree @@ -116,19 +118,24 @@ def _expand_macros(elements, macros, tokens): def _expand_macro(element, expand_el, macros, tokens): macro_name = expand_el.get('macro') - macro_def = deepcopy(macros[macro_name]) + macro_def = macros[macro_name] + expanded_elements = deepcopy(macro_def.elements) - _expand_yield_statements(macro_def, expand_el) + _expand_yield_statements(expanded_elements, expand_el) # Recursively expand contained macros. - _expand_macros(macro_def, macros, tokens) + _expand_macros(expanded_elements, macros, tokens) + macro_tokens = macro_def.macro_tokens(expand_el) + if macro_tokens: + print macro_tokens + _expand_tokens(expanded_elements, macro_tokens) # 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) + _xml_replace(expand_el, expanded_elements, parent_map) def _expand_yield_statements(macro_def, expand_el): @@ -233,6 +240,34 @@ def _xml_replace(query, targets, parent_map): parent_el.remove(query) +class XmlMacroDef(object): + + def __init__(self, el): + self.elements = list(el) + parameters = {} + for key, value in el.attrib.items(): + for char, char_descript in [("@", "at"), ("$", "dollar")]: + if key == "%s_tokens" % char_descript: + for at_token in value.split(","): + parameter_name = at_token + parameters[parameter_name] = (char, REQUIRED_PARAMETER) + elif key.startswith("%s_token_" % char_descript): + parameter_name = key[len("%s_token_" % char_descript):] + parameters[parameter_name] = (char, value) + self.parameters = parameters + + def macro_tokens(self, expand_el): + tokens = {} + for key, (wrap_char, default_val) in self.parameters.items(): + token_value = expand_el.attrib.get(key, default_val) + if token_value is REQUIRED_PARAMETER: + message = "Failed to expand macro - missing required parameter [%s]." + raise ValueError(message % key) + token_name = "%s%s%s" % (wrap_char, key.upper(), wrap_char) + tokens[token_name] = token_value + return tokens + + def _parse_xml(fname): tree = ElementTree.parse(fname) root = tree.getroot() diff --git a/test/unit/tools/test_tool_loader.py b/test/unit/tools/test_tool_loader.py index 59c90bbd298..84a9789c6e4 100644 --- a/test/unit/tools/test_tool_loader.py +++ b/test/unit/tools/test_tool_loader.py @@ -189,3 +189,41 @@ def test_loader(): tag_el = xml.find("another").find("tag") value = tag_el.get('value') assert value == "The value.", value + + # Test macros XML macros with $ expansions in attributes + with TestToolDirectory() as tool_dir: + tool_dir.write(''' + + + + + + + + +''') + xml = tool_dir.load() + input_els = xml.findall("inputs") + assert len(input_els) == 1 + assert input_els[0].attrib["type"] == "the type is hello" + + # Test macros XML macros with @ expansions in text + with TestToolDirectory() as tool_dir: + tool_dir.write(''' + + + + + + + @FOO@ + + + +''') + xml = tool_dir.load() + input_els = xml.findall("inputs") + assert len(input_els) == 3 + assert input_els[0].text == "hello" + assert input_els[1].text == "world" + assert input_els[2].text == "the_default"