Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/galaxy/util/xml_macros.py @ 1:56ad4e20f292 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
| author | guerler |
|---|---|
| date | Fri, 31 Jul 2020 00:32:28 -0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 0:d30785e31577 | 1:56ad4e20f292 |
|---|---|
| 1 import os | |
| 2 from copy import deepcopy | |
| 3 | |
| 4 from galaxy.util import parse_xml | |
| 5 | |
| 6 REQUIRED_PARAMETER = object() | |
| 7 | |
| 8 | |
| 9 def load_with_references(path): | |
| 10 """Load XML documentation from file system and preprocesses XML macros. | |
| 11 | |
| 12 Return the XML representation of the expanded tree and paths to | |
| 13 referenced files that were imported (macros). | |
| 14 """ | |
| 15 tree = raw_xml_tree(path) | |
| 16 root = tree.getroot() | |
| 17 | |
| 18 macro_paths = _import_macros(root, path) | |
| 19 | |
| 20 # Collect tokens | |
| 21 tokens = _macros_of_type(root, 'token', lambda el: el.text or '') | |
| 22 tokens = expand_nested_tokens(tokens) | |
| 23 | |
| 24 # Expand xml macros | |
| 25 macro_dict = _macros_of_type(root, 'xml', lambda el: XmlMacroDef(el)) | |
| 26 _expand_macros([root], macro_dict, tokens) | |
| 27 | |
| 28 return tree, macro_paths | |
| 29 | |
| 30 | |
| 31 def load(path): | |
| 32 tree, _ = load_with_references(path) | |
| 33 return tree | |
| 34 | |
| 35 | |
| 36 def template_macro_params(root): | |
| 37 """ | |
| 38 Look for template macros and populate param_dict (for cheetah) | |
| 39 with these. | |
| 40 """ | |
| 41 param_dict = {} | |
| 42 macro_dict = _macros_of_type(root, 'template', lambda el: el.text) | |
| 43 for key, value in macro_dict.items(): | |
| 44 param_dict[key] = value | |
| 45 return param_dict | |
| 46 | |
| 47 | |
| 48 def raw_xml_tree(path): | |
| 49 """ Load raw (no macro expansion) tree representation of XML represented | |
| 50 at the specified path. | |
| 51 """ | |
| 52 tree = parse_xml(path, strip_whitespace=False, remove_comments=True) | |
| 53 return tree | |
| 54 | |
| 55 | |
| 56 def imported_macro_paths(root): | |
| 57 macros_el = _macros_el(root) | |
| 58 return _imported_macro_paths_from_el(macros_el) | |
| 59 | |
| 60 | |
| 61 def _import_macros(root, path): | |
| 62 xml_base_dir = os.path.dirname(path) | |
| 63 macros_el = _macros_el(root) | |
| 64 if macros_el is not None: | |
| 65 macro_els, macro_paths = _load_macros(macros_el, xml_base_dir) | |
| 66 _xml_set_children(macros_el, macro_els) | |
| 67 return macro_paths | |
| 68 | |
| 69 | |
| 70 def _macros_el(root): | |
| 71 return root.find('macros') | |
| 72 | |
| 73 | |
| 74 def _macros_of_type(root, type, el_func): | |
| 75 macros_el = root.find('macros') | |
| 76 macro_dict = {} | |
| 77 if macros_el is not None: | |
| 78 macro_els = macros_el.findall('macro') | |
| 79 filtered_els = [(macro_el.get("name"), el_func(macro_el)) | |
| 80 for macro_el in macro_els | |
| 81 if macro_el.get('type') == type] | |
| 82 macro_dict = dict(filtered_els) | |
| 83 return macro_dict | |
| 84 | |
| 85 | |
| 86 def expand_nested_tokens(tokens): | |
| 87 for token_name in tokens.keys(): | |
| 88 for current_token_name, current_token_value in tokens.items(): | |
| 89 if token_name in current_token_value: | |
| 90 if token_name == current_token_name: | |
| 91 raise Exception("Token '%s' cannot contain itself" % token_name) | |
| 92 tokens[current_token_name] = current_token_value.replace(token_name, tokens[token_name]) | |
| 93 return tokens | |
| 94 | |
| 95 | |
| 96 def _expand_tokens(elements, tokens): | |
| 97 if not tokens or elements is None: | |
| 98 return | |
| 99 | |
| 100 for element in elements: | |
| 101 _expand_tokens_for_el(element, tokens) | |
| 102 | |
| 103 | |
| 104 def _expand_tokens_for_el(element, tokens): | |
| 105 value = element.text | |
| 106 if value: | |
| 107 new_value = _expand_tokens_str(element.text, tokens) | |
| 108 if not (new_value is value): | |
| 109 element.text = new_value | |
| 110 for key, value in element.attrib.items(): | |
| 111 new_value = _expand_tokens_str(value, tokens) | |
| 112 if not (new_value is value): | |
| 113 element.attrib[key] = new_value | |
| 114 _expand_tokens(list(element), tokens) | |
| 115 | |
| 116 | |
| 117 def _expand_tokens_str(s, tokens): | |
| 118 for key, value in tokens.items(): | |
| 119 if key in s: | |
| 120 s = s.replace(key, value) | |
| 121 return s | |
| 122 | |
| 123 | |
| 124 def _expand_macros(elements, macros, tokens): | |
| 125 if not macros and not tokens: | |
| 126 return | |
| 127 | |
| 128 for element in elements: | |
| 129 while True: | |
| 130 expand_el = element.find('.//expand') | |
| 131 if expand_el is None: | |
| 132 break | |
| 133 _expand_macro(element, expand_el, macros, tokens) | |
| 134 | |
| 135 _expand_tokens_for_el(element, tokens) | |
| 136 | |
| 137 | |
| 138 def _expand_macro(element, expand_el, macros, tokens): | |
| 139 macro_name = expand_el.get('macro') | |
| 140 macro_def = macros[macro_name] | |
| 141 expanded_elements = deepcopy(macro_def.elements) | |
| 142 | |
| 143 _expand_yield_statements(expanded_elements, expand_el) | |
| 144 | |
| 145 # Recursively expand contained macros. | |
| 146 _expand_macros(expanded_elements, macros, tokens) | |
| 147 macro_tokens = macro_def.macro_tokens(expand_el) | |
| 148 if macro_tokens: | |
| 149 _expand_tokens(expanded_elements, macro_tokens) | |
| 150 | |
| 151 # HACK for elementtree, newer implementations (etree/lxml) won't | |
| 152 # require this parent_map data structure but elementtree does not | |
| 153 # track parents or recognize .find('..'). | |
| 154 # TODO fix this now that we're not using elementtree | |
| 155 parent_map = dict((c, p) for p in element.iter() for c in p) | |
| 156 _xml_replace(expand_el, expanded_elements, parent_map) | |
| 157 | |
| 158 | |
| 159 def _expand_yield_statements(macro_def, expand_el): | |
| 160 yield_els = [yield_el for macro_def_el in macro_def for yield_el in macro_def_el.findall('.//yield')] | |
| 161 | |
| 162 expand_el_children = list(expand_el) | |
| 163 macro_def_parent_map = \ | |
| 164 dict((c, p) for macro_def_el in macro_def for p in macro_def_el.iter() for c in p) | |
| 165 | |
| 166 for yield_el in yield_els: | |
| 167 _xml_replace(yield_el, expand_el_children, macro_def_parent_map) | |
| 168 | |
| 169 # Replace yields at the top level of a macro, seems hacky approach | |
| 170 replace_yield = True | |
| 171 while replace_yield: | |
| 172 for i, macro_def_el in enumerate(macro_def): | |
| 173 if macro_def_el.tag == "yield": | |
| 174 for target in expand_el_children: | |
| 175 i += 1 | |
| 176 macro_def.insert(i, target) | |
| 177 macro_def.remove(macro_def_el) | |
| 178 continue | |
| 179 | |
| 180 replace_yield = False | |
| 181 | |
| 182 | |
| 183 def _load_macros(macros_el, xml_base_dir): | |
| 184 macros = [] | |
| 185 # Import macros from external files. | |
| 186 imported_macros, macro_paths = _load_imported_macros(macros_el, xml_base_dir) | |
| 187 macros.extend(imported_macros) | |
| 188 # Load all directly defined macros. | |
| 189 macros.extend(_load_embedded_macros(macros_el, xml_base_dir)) | |
| 190 return macros, macro_paths | |
| 191 | |
| 192 | |
| 193 def _load_embedded_macros(macros_el, xml_base_dir): | |
| 194 macros = [] | |
| 195 | |
| 196 macro_els = [] | |
| 197 # attribute typed macro | |
| 198 if macros_el is not None: | |
| 199 macro_els = macros_el.findall("macro") | |
| 200 for macro in macro_els: | |
| 201 if 'type' not in macro.attrib: | |
| 202 macro.attrib['type'] = 'xml' | |
| 203 macros.append(macro) | |
| 204 | |
| 205 # type shortcuts (<xml> is a shortcut for <macro type="xml", | |
| 206 # likewise for <template>. | |
| 207 typed_tag = ['template', 'xml', 'token'] | |
| 208 for tag in typed_tag: | |
| 209 macro_els = [] | |
| 210 if macros_el is not None: | |
| 211 macro_els = macros_el.findall(tag) | |
| 212 for macro_el in macro_els: | |
| 213 macro_el.attrib['type'] = tag | |
| 214 macro_el.tag = 'macro' | |
| 215 macros.append(macro_el) | |
| 216 | |
| 217 return macros | |
| 218 | |
| 219 | |
| 220 def _load_imported_macros(macros_el, xml_base_dir): | |
| 221 macros = [] | |
| 222 macro_paths = [] | |
| 223 | |
| 224 for tool_relative_import_path in _imported_macro_paths_from_el(macros_el): | |
| 225 import_path = \ | |
| 226 os.path.join(xml_base_dir, tool_relative_import_path) | |
| 227 macro_paths.append(import_path) | |
| 228 file_macros, current_macro_paths = _load_macro_file(import_path, xml_base_dir) | |
| 229 macros.extend(file_macros) | |
| 230 macro_paths.extend(current_macro_paths) | |
| 231 | |
| 232 return macros, macro_paths | |
| 233 | |
| 234 | |
| 235 def _imported_macro_paths_from_el(macros_el): | |
| 236 imported_macro_paths = [] | |
| 237 macro_import_els = [] | |
| 238 if macros_el is not None: | |
| 239 macro_import_els = macros_el.findall("import") | |
| 240 for macro_import_el in macro_import_els: | |
| 241 raw_import_path = macro_import_el.text | |
| 242 imported_macro_paths.append(raw_import_path) | |
| 243 return imported_macro_paths | |
| 244 | |
| 245 | |
| 246 def _load_macro_file(path, xml_base_dir): | |
| 247 tree = parse_xml(path, strip_whitespace=False) | |
| 248 root = tree.getroot() | |
| 249 return _load_macros(root, xml_base_dir) | |
| 250 | |
| 251 | |
| 252 def _xml_set_children(element, new_children): | |
| 253 for old_child in element: | |
| 254 element.remove(old_child) | |
| 255 for i, new_child in enumerate(new_children): | |
| 256 element.insert(i, new_child) | |
| 257 | |
| 258 | |
| 259 def _xml_replace(query, targets, parent_map): | |
| 260 # parent_el = query.find('..') ## Something like this would be better with newer xml library | |
| 261 parent_el = parent_map[query] | |
| 262 matching_index = -1 | |
| 263 # for index, el in enumerate(parent_el.iter('.')): ## Something like this for newer implementation | |
| 264 for index, el in enumerate(list(parent_el)): | |
| 265 if el == query: | |
| 266 matching_index = index | |
| 267 break | |
| 268 assert matching_index >= 0 | |
| 269 current_index = matching_index | |
| 270 for target in targets: | |
| 271 current_index += 1 | |
| 272 parent_el.insert(current_index, deepcopy(target)) | |
| 273 parent_el.remove(query) | |
| 274 | |
| 275 | |
| 276 class XmlMacroDef(object): | |
| 277 | |
| 278 def __init__(self, el): | |
| 279 self.elements = list(el) | |
| 280 parameters = {} | |
| 281 tokens = [] | |
| 282 token_quote = "@" | |
| 283 for key, value in el.attrib.items(): | |
| 284 if key == "token_quote": | |
| 285 token_quote = value | |
| 286 if key == "tokens": | |
| 287 for token in value.split(","): | |
| 288 tokens.append((token, REQUIRED_PARAMETER)) | |
| 289 elif key.startswith("token_"): | |
| 290 token = key[len("token_"):] | |
| 291 tokens.append((token, value)) | |
| 292 for name, default in tokens: | |
| 293 parameters[name] = (token_quote, default) | |
| 294 self.parameters = parameters | |
| 295 | |
| 296 def macro_tokens(self, expand_el): | |
| 297 tokens = {} | |
| 298 for key, (wrap_char, default_val) in self.parameters.items(): | |
| 299 token_value = expand_el.attrib.get(key, default_val) | |
| 300 if token_value is REQUIRED_PARAMETER: | |
| 301 message = "Failed to expand macro - missing required parameter [%s]." | |
| 302 raise ValueError(message % key) | |
| 303 token_name = "%s%s%s" % (wrap_char, key.upper(), wrap_char) | |
| 304 tokens[token_name] = token_value | |
| 305 return tokens | |
| 306 | |
| 307 | |
| 308 __all__ = ( | |
| 309 "imported_macro_paths", | |
| 310 "load", | |
| 311 "load_with_references", | |
| 312 "raw_xml_tree", | |
| 313 "template_macro_params", | |
| 314 ) |
