# TODO: Add examples, tables and best practice links to command # TODO: Examples of truevalue, falsevalue # TODO: Test param extra_file # Things dropped from schema_template.md (still documented inside schema). # - request_parameter_translation import sys from io import StringIO from lxml import etree from yaml import safe_load with open(sys.argv[2]) as f: xmlschema_doc = etree.parse(f) markdown_buffer = StringIO() DIRECTIVES_PATH = "../client/src/components/Markdown/directives.yml" DIRECTIVES = safe_load(open(DIRECTIVES_PATH)) def main(): """Entry point for the function that builds Markdown help for the Galaxy XSD.""" content_list = [] found_tag = False with open(sys.argv[1]) as markdown_template: for line in markdown_template: if line.startswith("$tag:"): found_tag = True tag = Tag(line.rstrip()) content_list.append(tag.build_help()) elif not found_tag: print(line, end="") else: raise Exception("No normal text allowed after the first $tag") for el in content_list: print(el, end="") class Tag: def __init__(self, line: str): assert line.startswith("$tag:") line_parts = line.split(" ") first_part = line_parts[0] hide_attributes = False if len(line_parts) > 1: if "hide_attributes" in line_parts[1]: hide_attributes = True _, title, xpath = first_part.split(":") xpath = xpath.replace("/element", "/{http://www.w3.org/2001/XMLSchema}element") xpath = xpath.replace("/group", "/{http://www.w3.org/2001/XMLSchema}group") xpath = xpath.replace("/complexType", "/{http://www.w3.org/2001/XMLSchema}complexType") self.xpath = xpath self.hide_attributes = hide_attributes self.title = title @property def _pretty_title(self): return " > ".join(f"``{p}``" for p in self.title.split("|")) def build_help(self): tag = xmlschema_doc.find(self.xpath) if tag is None: raise Exception(f"Could not find xpath for {self.xpath}") tag_help = StringIO() tag_help.write("## " + self._pretty_title) tag_help.write("\n\n") tag_help.write(_build_tag(tag, self.hide_attributes)) tag_help.write("\n\n") return tag_help.getvalue() def _build_tag(tag, hide_attributes): tag_el = _find_tag_el(tag) attributes = _find_attributes(tag) tag_help = StringIO() annotation_el = tag_el.find("{http://www.w3.org/2001/XMLSchema}annotation") text = annotation_el.find("{http://www.w3.org/2001/XMLSchema}documentation").text text = _replace_attribute_list(tag, text, attributes) text = _expand_directives(text) for line in text.splitlines(): if line.startswith("$assertions"): assertions_tag = xmlschema_doc.find( "//{http://www.w3.org/2001/XMLSchema}complexType[@name='TestAssertions']" ) assertions_buffer = StringIO() assertions_buffer.write(_doc_or_none(assertions_tag)) assertions_buffer.write("\n\n") assertion_tag = xmlschema_doc.find("//{http://www.w3.org/2001/XMLSchema}group[@name='TestAssertion']") elements = assertion_tag.findall( "{http://www.w3.org/2001/XMLSchema}choice/{http://www.w3.org/2001/XMLSchema}element" ) for element in elements: doc = _doc_or_none(element) if doc is None: doc = _doc_or_none(element) assert doc is not None, f"Documentation for {element.attrib['name']} is empty" doc = doc.strip() element_attributes = _find_attributes(element) doc = _replace_attribute_list(element, doc, element_attributes) assertions_buffer.write(f"#### ``{element.attrib['name']}``:\n\n{doc}\n\n") text = text.replace(line, assertions_buffer.getvalue()) tag_help.write(text) if best_practices := _get_bp_link(annotation_el): tag_help.write("\n\n### Best Practices\n") tag_help.write(f""" Find the Intergalactic Utilities Commission suggested best practices for this element [here]({best_practices}).""") tag_help.write(_build_attributes_table(tag, attributes, hide_attributes)) return tag_help.getvalue() def _replace_attribute_list(tag, text, attributes): for line in text.splitlines(): if not line.startswith("$attribute_list:"): continue attributes_str, header_level = line.split(":")[1:3] if attributes_str == "": attribute_names = None else: attribute_names = attributes_str.split(",") header_level = int(header_level) text = text.replace( line, _build_attributes_table(tag, attributes, attribute_names=attribute_names, header_level=header_level) ) return text def _build_directive_table(line: str) -> str: _, directives_str = line.split(":", 1) directives = directives_str.split(",") attribute_table = StringIO() attribute_table.write("\n\n") for directive in directives: header_level = 3 header_prefix = "#" * header_level attribute_table.write(f"\n{header_prefix} {directive}\n\n") directive_info = DIRECTIVES[directive] if "help" in directive_info: attribute_table.write(DIRECTIVES[directive]["help"]) return attribute_table.getvalue() def _expand_directives(text): for line in text.splitlines(): if not line.startswith("$directive_list:"): continue text = text.replace(line, _build_directive_table(line)) return text def _get_bp_link(annotation_el): anchor = annotation_el.attrib.get("{http://galaxyproject.org/xml/1.0}best_practices", None) link = None if anchor: link = f"https://galaxy-iuc-standards.readthedocs.io/en/latest/best_practices/tool_xml.html#{anchor}" return link def _build_attributes_table(tag, attributes, hide_attributes=False, attribute_names=None, header_level=3): attribute_table = StringIO() attribute_table.write("\n\n") if attributes and not hide_attributes: header_prefix = "#" * header_level attribute_table.write(f"\n{header_prefix} Attributes\n\n") attribute_table.write("Attribute | Details | Required\n") attribute_table.write("--- | --- | ---\n") for attribute in attributes: name = attribute.attrib["name"] if attribute_names and name not in attribute_names: continue details = _doc_or_none(attribute) if details is None: type_el = _type_el(attribute) assert type_el is not None, f"No details or type found for {name}" details = _doc_or_none(type_el) annotation_el = type_el.find("{http://www.w3.org/2001/XMLSchema}annotation") else: annotation_el = attribute.find("{http://www.w3.org/2001/XMLSchema}annotation") use = attribute.attrib.get("use", "optional") == "required" details = details.replace("\n", " ").strip() details = details.replace("|", "\\|").strip() best_practices = _get_bp_link(annotation_el) if best_practices: details += f""" Find the Intergalactic Utilities Commission suggested best practices for this element [here]({best_practices}).""" attribute_table.write(f"``{name}`` | {details} | {use}\n") return attribute_table.getvalue() def _find_attributes(tag): raw_attributes = ( tag.findall("{http://www.w3.org/2001/XMLSchema}attribute") or tag.findall("{http://www.w3.org/2001/XMLSchema}complexType/{http://www.w3.org/2001/XMLSchema}attribute") or tag.findall( "{http://www.w3.org/2001/XMLSchema}complexContent/{http://www.w3.org/2001/XMLSchema}extension/{http://www.w3.org/2001/XMLSchema}attribute" ) or tag.findall( "{http://www.w3.org/2001/XMLSchema}simpleContent/{http://www.w3.org/2001/XMLSchema}extension/{http://www.w3.org/2001/XMLSchema}attribute" ) ) attribute_groups = ( tag.findall("{http://www.w3.org/2001/XMLSchema}attributeGroup") or tag.findall("{http://www.w3.org/2001/XMLSchema}complexType/{http://www.w3.org/2001/XMLSchema}attributeGroup") or tag.findall( "{http://www.w3.org/2001/XMLSchema}complexContent/{http://www.w3.org/2001/XMLSchema}extension/{http://www.w3.org/2001/XMLSchema}attributeGroup" ) or tag.findall( "{http://www.w3.org/2001/XMLSchema}simpleContent/{http://www.w3.org/2001/XMLSchema}extension/{http://www.w3.org/2001/XMLSchema}attributeGroup" ) ) attributes = list(raw_attributes) for attribute_group in attribute_groups: attribute_group_name = attribute_group.get("ref") attribute_group_def = xmlschema_doc.find( f"//{{http://www.w3.org/2001/XMLSchema}}attributeGroup/[@name='{attribute_group_name}']" ) attributes.extend(_find_attributes(attribute_group_def)) return attributes def _find_tag_el(tag): if _doc_or_none(tag) is not None: return tag return _type_el(tag) def _type_el(tag): element_type = tag.attrib["type"] type_el = xmlschema_doc.find(f"//{{http://www.w3.org/2001/XMLSchema}}complexType/[@name='{element_type}']") if type_el is None: type_el = xmlschema_doc.find(f"//{{http://www.w3.org/2001/XMLSchema}}simpleType/[@name='{element_type}']") return type_el def _doc_or_none(tag): doc_el = tag.find("{http://www.w3.org/2001/XMLSchema}annotation/{http://www.w3.org/2001/XMLSchema}documentation") if doc_el is None: return None else: return doc_el.text if __name__ == "__main__": main()