def process_needfilters(app, doctree, fromdocname): # Replace all needlist nodes with a list of the collected needs. # Augment each need with a backlink to the original location. env = app.builder.env # NEEDFILTER for node in doctree.traverse(Needfilter): if not app.config.needs_include_needs: # Ok, this is really dirty. # If we replace a node, docutils checks, if it will not lose any attributes. # But this is here the case, because we are using the attribute "ids" of a node. # However, I do not understand, why losing an attribute is such a big deal, so we delete everything # before docutils claims about it. for att in ('ids', 'names', 'classes', 'dupnames'): node[att] = [] node.replace_self([]) continue id = node.attributes["ids"][0] current_needlist = env.need_all_needlists[id] all_needs = env.need_all_needs if current_needlist["layout"] == "list": content = [] elif current_needlist["layout"] == "diagram": content = [] try: if "sphinxcontrib.plantuml" not in app.config.extensions: raise ImportError from sphinxcontrib.plantuml import plantuml except ImportError: content = nodes.error() para = nodes.paragraph() text = nodes.Text("PlantUML is not available!", "PlantUML is not available!") para += text content.append(para) node.replace_self(content) continue plantuml_block_text = ".. plantuml::\n" \ "\n" \ " @startuml" \ " @enduml" puml_node = plantuml(plantuml_block_text, **dict()) puml_node["uml"] = "@startuml\n" puml_connections = "" elif current_needlist["layout"] == "table": content = nodes.table() tgroup = nodes.tgroup() id_colspec = nodes.colspec(colwidth=5) title_colspec = nodes.colspec(colwidth=15) type_colspec = nodes.colspec(colwidth=5) status_colspec = nodes.colspec(colwidth=5) links_colspec = nodes.colspec(colwidth=5) tags_colspec = nodes.colspec(colwidth=5) tgroup += [ id_colspec, title_colspec, type_colspec, status_colspec, links_colspec, tags_colspec ] tgroup += nodes.thead( '', nodes.row('', nodes.entry('', nodes.paragraph('', 'ID')), nodes.entry('', nodes.paragraph('', 'Title')), nodes.entry('', nodes.paragraph('', 'Type')), nodes.entry('', nodes.paragraph('', 'Status')), nodes.entry('', nodes.paragraph('', 'Links')), nodes.entry('', nodes.paragraph('', 'Tags')))) tbody = nodes.tbody() tgroup += tbody content += tgroup all_needs = list(all_needs.values()) if current_needlist["sort_by"] is not None: if current_needlist["sort_by"] == "id": all_needs = sorted(all_needs, key=lambda node: node["id"]) elif current_needlist["sort_by"] == "status": all_needs = sorted(all_needs, key=status_sorter) for need_info in all_needs: status_filter_passed = False if need_info["status"] is None or \ need_info["status"] in current_needlist["status"] or \ len(current_needlist["status"]) == 0: status_filter_passed = True tags_filter_passed = False if len(set(need_info["tags"]) & set(current_needlist["tags"])) > 0 or len( current_needlist["tags"]) == 0: tags_filter_passed = True type_filter_passed = False if need_info["type"] in current_needlist["types"] \ or need_info["type_name"] in current_needlist["types"] \ or len(current_needlist["types"]) == 0: type_filter_passed = True if current_needlist["filter"] is None: python_filter_passed = True else: python_filter_passed = False filter_context = { "tags": need_info["tags"], "status": need_info["status"], "type": need_info["type"], "id": need_info["id"], "title": need_info["title"], "links": need_info["links"], "content": need_info["content"], "search": re.search } try: # python_filter_passed = eval(current_needlist["filter"], globals(), filter_context) python_filter_passed = eval(current_needlist["filter"], None, filter_context) except Exception as e: print("Filter {0} not valid: Error: {1}".format( current_needlist["filter"], e)) if status_filter_passed and tags_filter_passed and type_filter_passed and python_filter_passed: if current_needlist["layout"] == "list": para = nodes.line() description = "%s: %s" % (need_info["id"], need_info["title"]) if current_needlist["show_status"] and need_info[ "status"] is not None: description += " (%s)" % need_info["status"] if current_needlist["show_tags"] and need_info[ "tags"] is not None: description += " [%s]" % "; ".join(need_info["tags"]) title = nodes.Text(description, description) # Create a reference if not need_info["hide"]: ref = nodes.reference('', '') ref['refdocname'] = need_info['docname'] ref['refuri'] = app.builder.get_relative_uri( fromdocname, need_info['docname']) ref['refuri'] += '#' + need_info['target_node']['refid'] ref.append(title) para += ref else: para += title content.append(para) elif current_needlist["layout"] == "table": row = nodes.row() row += row_col_maker(app, fromdocname, env.need_all_needs, need_info, "id", make_ref=True) row += row_col_maker(app, fromdocname, env.need_all_needs, need_info, "title") row += row_col_maker(app, fromdocname, env.need_all_needs, need_info, "type_name") row += row_col_maker(app, fromdocname, env.need_all_needs, need_info, "status") row += row_col_maker(app, fromdocname, env.need_all_needs, need_info, "links", ref_lookup=True) row += row_col_maker(app, fromdocname, env.need_all_needs, need_info, "tags") tbody += row elif current_needlist["layout"] == "diagram": # Link calculation # All links we can get from docutils functions will be relative. # But the generated link in the svg will be relative to the svg-file location # (e.g. server.com/docs/_images/sqwxo499cnq329439dfjne.svg) # and not to current documentation. Therefore we need to add ../ to get out of the _image folder. try: link = "../" + app.builder.get_target_uri(need_info['docname']) \ + "?highlight={0}".format(urlParse(need_info['title'])) \ + "#" \ + need_info['target_node']['refid'] \ # Gets mostly called during latex generation except NoUri: link = "" diagram_template = Template( env.config.needs_diagram_template) node_text = diagram_template.render(**need_info) puml_node[ "uml"] += '{style} "{node_text}" as {id} [[{link}]] {color}\n'.format( id=need_info["id"], node_text=node_text, link=link, color=need_info["type_color"], style=need_info["type_style"]) for link in need_info["links"]: puml_connections += '{id} --> {link}\n'.format( id=need_info["id"], link=link) if current_needlist["layout"] == "diagram": puml_node["uml"] += puml_connections # Create a legend if current_needlist["show_legend"]: puml_node["uml"] += "legend\n" puml_node["uml"] += "|= Color |= Type |\n" for need in app.config.needs_types: puml_node[ "uml"] += "|<back:{color}> {color} </back>| {name} |\n".format( color=need["color"], name=need["title"]) puml_node["uml"] += "endlegend\n" puml_node["uml"] += "@enduml" puml_node["incdir"] = os.path.dirname(current_needlist["docname"]) puml_node["filename"] = os.path.split( current_needlist["docname"])[1] # Needed for plantuml >= 0.9 content.append(puml_node) if len(content) == 0: nothing_found = "No needs passed the filters" para = nodes.line() nothing_found_node = nodes.Text(nothing_found, nothing_found) para += nothing_found_node content.append(para) if current_needlist["show_filters"]: para = nodes.paragraph() filter_text = "Used filter:" filter_text += " status(%s)" % " OR ".join( current_needlist["status"]) if len( current_needlist["status"]) > 0 else "" if len(current_needlist["status"]) > 0 and len( current_needlist["tags"]) > 0: filter_text += " AND " filter_text += " tags(%s)" % " OR ".join( current_needlist["tags"]) if len( current_needlist["tags"]) > 0 else "" if (len(current_needlist["status"]) > 0 or len(current_needlist["tags"]) > 0) and len( current_needlist["types"]) > 0: filter_text += " AND " filter_text += " types(%s)" % " OR ".join( current_needlist["types"]) if len( current_needlist["types"]) > 0 else "" filter_node = nodes.emphasis(filter_text, filter_text) para += filter_node content.append(para) node.replace_self(content)
def process_needtables(app, doctree, fromdocname): """ Replace all needtables nodes with a table of filtered nodes. :param app: :param doctree: :param fromdocname: :return: """ env = app.builder.env # Create a link_type dictionary, which keys-list can be easily used to find columns link_type_list = {} for link_type in app.config.needs_extra_links: link_type_list[link_type["option"].upper()] = link_type link_type_list[link_type["option"].upper() + "_BACK"] = link_type link_type_list[link_type["incoming"].upper()] = link_type link_type_list[link_type["outgoing"].upper()] = link_type # Extra handling tb backward compatible, as INCOMING and OUTGOING are # known und used column names for incoming/outgoing links if link_type["option"] == "links": link_type_list["OUTGOING"] = link_type link_type_list["INCOMING"] = link_type for node in doctree.traverse(Needtable): if not app.config.needs_include_needs: # Ok, this is really dirty. # If we replace a node, docutils checks, if it will not lose any attributes. # If we replace a node, docutils checks, if it will not lose any attributes. # But this is here the case, because we are using the attribute "ids" of a node. # However, I do not understand, why losing an attribute is such a big deal, so we delete everything # before docutils claims about it. for att in ("ids", "names", "classes", "dupnames"): node[att] = [] node.replace_self([]) continue id = node.attributes["ids"][0] current_needtable = env.need_all_needtables[id] all_needs = env.needs_all_needs if current_needtable["style"] == "" or current_needtable[ "style"].upper() not in ["TABLE", "DATATABLES"]: if app.config.needs_table_style == "": style = "DATATABLES" else: style = app.config.needs_table_style.upper() else: style = current_needtable["style"].upper() # Prepare table classes = [f"NEEDS_{style}"] + current_needtable["classes"] # Only add the theme specific "do not touch this table" class, if we use a style which # care about table layout and styling. The normal "TABLE" style is using the Sphinx default table # css classes and therefore must be handled by the themes. if style != "TABLE": classes.extend(app.config.needs_table_classes) content = nodes.table(classes=classes) tgroup = nodes.tgroup() # Define Table column width colwidths = current_needtable["colwidths"] for index, value in enumerate(current_needtable["columns"]): option, _title = value if colwidths: # Get values from given colwidths option tgroup += nodes.colspec(colwidth=colwidths[index]) elif option == "TITLE": # if nothing in colwidths... tgroup += nodes.colspec(colwidth=15) else: tgroup += nodes.colspec(colwidth=5) node_columns = [] for _option, title in current_needtable["columns"]: header_name = title node_columns.append( nodes.entry("", nodes.paragraph("", header_name))) tgroup += nodes.thead("", nodes.row("", *node_columns)) tbody = nodes.tbody() tgroup += tbody content += tgroup # Add lineno to node content.line = current_needtable["lineno"] all_needs = list(all_needs.values()) # Perform filtering of needs try: found_needs = process_filters(app, all_needs, current_needtable) except Exception as e: raise e def get_sorter(key): """ Returns a sort-function for a given need-key. :param key: key of need object as string :return: function to use in sort(key=x) """ def sort(need): """ Returns a given value of need, which is used for list sorting. :param need: need-element, which gets sort :return: value of need """ if isinstance(need[key], str): # if we filter for string (e.g. id) everything should be lowercase. # Otherwise "Z" will be above "a" return need[key].lower() return need[key] return sort found_needs.sort(key=get_sorter(current_needtable["sort"])) for need_info in found_needs: style_row = check_and_get_content(current_needtable["style_row"], need_info, env) style_row = style_row.replace( " ", "_") # Replace whitespaces with _ to get valid css name temp_need = need_info.copy() if temp_need["is_need"]: row = nodes.row(classes=["need", style_row]) prefix = "" else: row = nodes.row(classes=["need_part", style_row]) temp_need["id"] = temp_need["id_complete"] prefix = app.config.needs_part_prefix temp_need["title"] = temp_need["content"] for option, _title in current_needtable["columns"]: if option == "ID": row += row_col_maker(app, fromdocname, env.needs_all_needs, temp_need, "id", make_ref=True, prefix=prefix) elif option == "TITLE": row += row_col_maker(app, fromdocname, env.needs_all_needs, temp_need, "title", prefix=prefix) elif option in link_type_list: link_type = link_type_list[option] if option in [ "INCOMING", link_type["option"].upper() + "_BACK", link_type["incoming"].upper() ]: row += row_col_maker( app, fromdocname, env.needs_all_needs, temp_need, link_type["option"] + "_back", ref_lookup=True, ) else: row += row_col_maker(app, fromdocname, env.needs_all_needs, temp_need, link_type["option"], ref_lookup=True) else: row += row_col_maker(app, fromdocname, env.needs_all_needs, temp_need, option.lower()) tbody += row # Need part rows if current_needtable["show_parts"] and need_info["is_need"]: for part in need_info["parts"].values(): # update the part with all information from its parent # this is required to make ID links work temp_part = part.copy( ) # The dict has to be manipulated, so that row_col_maker() can be used temp_part = {**need_info, **temp_part} temp_part[ "id_complete"] = f"{need_info['id']}.{temp_part['id']}" temp_part["id_parent"] = need_info["id"] temp_part["docname"] = need_info["docname"] row = nodes.row(classes=["need_part"]) for option, _title in current_needtable["columns"]: if option == "ID": row += row_col_maker( app, fromdocname, env.needs_all_needs, temp_part, "id_complete", make_ref=True, prefix=app.config.needs_part_prefix, ) elif option == "TITLE": row += row_col_maker( app, fromdocname, env.needs_all_needs, temp_part, "content", prefix=app.config.needs_part_prefix, ) elif option in link_type_list and (option in [ "INCOMING", link_type_list[option]["option"].upper() + "_BACK", link_type_list[option]["incoming"].upper(), ]): row += row_col_maker( app, fromdocname, env.needs_all_needs, temp_part, link_type_list[option]["option"] + "_back", ref_lookup=True, ) else: row += row_col_maker(app, fromdocname, env.needs_all_needs, temp_part, option.lower()) tbody += row if len(found_needs) == 0: content.append(no_needs_found_paragraph()) # add filter information to output if current_needtable["show_filters"]: content.append(used_filter_paragraph(current_needtable)) if current_needtable["caption"]: title_text = current_needtable["caption"] title = nodes.title(title_text, "", nodes.Text(title_text)) content.insert(0, title) # Put the table in a div-wrapper, so that we can control overflow / scroll layout if style == "TABLE": table_wrapper = nodes.container(classes=["needstable_wrapper"]) table_wrapper.insert(0, content) node.replace_self(table_wrapper) else: node.replace_self(content)
def process_needfilters(app, doctree, fromdocname): # Replace all needlist nodes with a list of the collected needs. # Augment each need with a backlink to the original location. env = app.builder.env # NEEDFILTER for node in doctree.traverse(Needfilter): if not app.config.needs_include_needs: # Ok, this is really dirty. # If we replace a node, docutils checks, if it will not lose any attributes. # But this is here the case, because we are using the attribute "ids" of a node. # However, I do not understand, why losing an attribute is such a big deal, so we delete everything # before docutils claims about it. for att in ("ids", "names", "classes", "dupnames"): node[att] = [] node.replace_self([]) continue id = node.attributes["ids"][0] current_needfilter = env.need_all_needfilters[id] all_needs = env.needs_all_needs if current_needfilter["layout"] == "list": content = [] elif current_needfilter["layout"] == "diagram": content = [] try: if "sphinxcontrib.plantuml" not in app.config.extensions: raise ImportError from sphinxcontrib.plantuml import plantuml except ImportError: content = nodes.error() para = nodes.paragraph() text = nodes.Text("PlantUML is not available!", "PlantUML is not available!") para += text content.append(para) node.replace_self(content) continue plantuml_block_text = ".. plantuml::\n" "\n" " @startuml" " @enduml" puml_node = plantuml(plantuml_block_text) puml_node["uml"] = "@startuml\n" puml_connections = "" elif current_needfilter["layout"] == "table": content = nodes.table() tgroup = nodes.tgroup() id_colspec = nodes.colspec(colwidth=5) title_colspec = nodes.colspec(colwidth=15) type_colspec = nodes.colspec(colwidth=5) status_colspec = nodes.colspec(colwidth=5) links_colspec = nodes.colspec(colwidth=5) tags_colspec = nodes.colspec(colwidth=5) tgroup += [id_colspec, title_colspec, type_colspec, status_colspec, links_colspec, tags_colspec] tgroup += nodes.thead( "", nodes.row( "", nodes.entry("", nodes.paragraph("", "ID")), nodes.entry("", nodes.paragraph("", "Title")), nodes.entry("", nodes.paragraph("", "Type")), nodes.entry("", nodes.paragraph("", "Status")), nodes.entry("", nodes.paragraph("", "Links")), nodes.entry("", nodes.paragraph("", "Tags")), ), ) tbody = nodes.tbody() tgroup += tbody content += tgroup all_needs = list(all_needs.values()) found_needs = process_filters(app, all_needs, current_needfilter) line_block = nodes.line_block() for need_info in found_needs: if current_needfilter["layout"] == "list": para = nodes.line() description = "{}: {}".format(need_info["id"], need_info["title"]) if current_needfilter["show_status"] and need_info["status"]: description += " (%s)" % need_info["status"] if current_needfilter["show_tags"] and need_info["tags"]: description += " [%s]" % "; ".join(need_info["tags"]) title = nodes.Text(description, description) # Create a reference if need_info["hide"]: para += title else: ref = nodes.reference("", "") ref["refdocname"] = need_info["docname"] ref["refuri"] = app.builder.get_relative_uri(fromdocname, need_info["docname"]) ref["refuri"] += "#" + need_info["target_node"]["refid"] ref.append(title) para += ref line_block.append(para) elif current_needfilter["layout"] == "table": row = nodes.row() row += row_col_maker(app, fromdocname, env.needs_all_needs, need_info, "id", make_ref=True) row += row_col_maker(app, fromdocname, env.needs_all_needs, need_info, "title") row += row_col_maker(app, fromdocname, env.needs_all_needs, need_info, "type_name") row += row_col_maker(app, fromdocname, env.needs_all_needs, need_info, "status") row += row_col_maker(app, fromdocname, env.needs_all_needs, need_info, "links", ref_lookup=True) row += row_col_maker(app, fromdocname, env.needs_all_needs, need_info, "tags") tbody += row elif current_needfilter["layout"] == "diagram": # Link calculation # All links we can get from docutils functions will be relative. # But the generated link in the svg will be relative to the svg-file location # (e.g. server.com/docs/_images/sqwxo499cnq329439dfjne.svg) # and not to current documentation. Therefore we need to add ../ to get out of the image folder. try: link = ( "../" + app.builder.get_target_uri(need_info["docname"]) + "?highlight={}".format(urlparse(need_info["title"])) + "#" + need_info["target_node"]["refid"] ) # Gets mostly called during latex generation except NoUri: link = "" diagram_template = Template(env.config.needs_diagram_template) node_text = diagram_template.render(**need_info) puml_node["uml"] += '{style} "{node_text}" as {id} [[{link}]] {color}\n'.format( id=need_info["id"], node_text=node_text, link=link, color=need_info["type_color"], style=need_info["type_style"], ) for link in need_info["links"]: puml_connections += "{id} --> {link}\n".format(id=need_info["id"], link=link) if current_needfilter["layout"] == "list": content.append(line_block) if current_needfilter["layout"] == "diagram": puml_node["uml"] += puml_connections # Create a legend if current_needfilter["show_legend"]: puml_node["uml"] += create_legend(app.config.needs_types) puml_node["uml"] += "@enduml" puml_node["incdir"] = os.path.dirname(current_needfilter["docname"]) puml_node["filename"] = os.path.split(current_needfilter["docname"])[1] # Needed for plantuml >= 0.9 content.append(puml_node) if len(content) == 0: nothing_found = "No needs passed the filters" para = nodes.line() nothing_found_node = nodes.Text(nothing_found, nothing_found) para += nothing_found_node content.append(para) if current_needfilter["show_filters"]: para = nodes.paragraph() filter_text = "Used filter:" filter_text += ( " status(%s)" % " OR ".join(current_needfilter["status"]) if len(current_needfilter["status"]) > 0 else "" ) if len(current_needfilter["status"]) > 0 and len(current_needfilter["tags"]) > 0: filter_text += " AND " filter_text += ( " tags(%s)" % " OR ".join(current_needfilter["tags"]) if len(current_needfilter["tags"]) > 0 else "" ) if (len(current_needfilter["status"]) > 0 or len(current_needfilter["tags"]) > 0) and len( current_needfilter["types"] ) > 0: filter_text += " AND " filter_text += ( " types(%s)" % " OR ".join(current_needfilter["types"]) if len(current_needfilter["types"]) > 0 else "" ) filter_node = nodes.emphasis(filter_text, filter_text) para += filter_node content.append(para) node.replace_self(content)
def process_needtables(app, doctree, fromdocname): """ Replace all needtables nodes with a tale of filtered noded. :param app: :param doctree: :param fromdocname: :return: """ env = app.builder.env # Create a link_type dictionary, which keys-list can be easily used to find columns link_type_list = {} for link_type in app.config.needs_extra_links: link_type_list[link_type["option"].upper()] = link_type link_type_list[link_type["option"].upper() + '_BACK'] = link_type link_type_list[link_type["incoming"].upper()] = link_type link_type_list[link_type["outgoing"].upper()] = link_type # Extra handling tb backward compatible, as INCOMING and OUTGOING are # known und used column names for incoming/outgoing links if link_type['option'] == 'links': link_type_list['OUTGOING'] = link_type link_type_list['INCOMING'] = link_type for node in doctree.traverse(Needtable): if not app.config.needs_include_needs: # Ok, this is really dirty. # If we replace a node, docutils checks, if it will not lose any attributes. # But this is here the case, because we are using the attribute "ids" of a node. # However, I do not understand, why losing an attribute is such a big deal, so we delete everything # before docutils claims about it. for att in ('ids', 'names', 'classes', 'dupnames'): node[att] = [] node.replace_self([]) continue id = node.attributes["ids"][0] current_needtable = env.need_all_needtables[id] all_needs = env.needs_all_needs if current_needtable["style"] == "" or current_needtable[ "style"].upper() not in ["TABLE", "DATATABLES"]: if app.config.needs_table_style == "": style = "DATATABLES" else: style = app.config.needs_table_style.upper() else: style = current_needtable["style"].upper() # Prepare table classes = ["NEEDS_{style}".format(style=style)] content = nodes.table(classes=classes) tgroup = nodes.tgroup() # Define Table column width # ToDo: Find a way to chosen to perfect width automatically. for col in current_needtable["columns"]: if col == "TITLE": tgroup += nodes.colspec(colwidth=15) else: tgroup += nodes.colspec(colwidth=5) node_columns = [] for col in current_needtable["columns"]: header_name = col.title() if col != "ID" else col header_name = header_name.replace("_", " ") node_columns.append( nodes.entry('', nodes.paragraph('', header_name))) tgroup += nodes.thead('', nodes.row('', *node_columns)) tbody = nodes.tbody() tgroup += tbody content += tgroup all_needs = list(all_needs.values()) # Perform filtering of needs found_needs = process_filters(all_needs, current_needtable) def get_sorter(key): """ Returns a sort-function for a given need-key. :param key: key of need object as string :return: function to use in sort(key=x) """ def sort(need): """ Returns a given value of need, which is used for list sorting. :param need: need-element, which gets sort :return: value of need """ if isinstance(need[key], str): # if we filter for string (e.g. id) everything should be lowercase. # Otherwise "Z" will be above "a" return need[key].lower() return need[key] return sort found_needs.sort(key=get_sorter(current_needtable['sort'])) for need_info in found_needs: style_row = check_and_get_content(current_needtable['style_row'], need_info, env) style_row = style_row.replace( ' ', '_') # Replace whitespaces with _ to get valid css name temp_need = need_info.copy() if temp_need['is_need']: row = nodes.row(classes=['need', style_row]) prefix = '' else: row = nodes.row(classes=['need_part', style_row]) temp_need['id'] = temp_need['id_complete'] prefix = app.config.needs_part_prefix temp_need['title'] = temp_need['content'] for col in current_needtable["columns"]: if col == "ID": row += row_col_maker(app, fromdocname, env.needs_all_needs, temp_need, "id", make_ref=True, prefix=prefix) elif col == "TITLE": row += row_col_maker(app, fromdocname, env.needs_all_needs, temp_need, "title", prefix=prefix) elif col in link_type_list.keys(): link_type = link_type_list[col] if col == 'INCOMING' or col == link_type['option'].upper() + '_BACK' or \ col == link_type['incoming'].upper(): row += row_col_maker(app, fromdocname, env.needs_all_needs, temp_need, link_type['option'] + '_back', ref_lookup=True) else: row += row_col_maker(app, fromdocname, env.needs_all_needs, temp_need, link_type['option'], ref_lookup=True) else: row += row_col_maker(app, fromdocname, env.needs_all_needs, temp_need, col.lower()) tbody += row # Need part rows if current_needtable["show_parts"] and need_info['is_need']: for key, part in need_info["parts"].items(): row = nodes.row(classes=['need_part']) temp_part = part.copy( ) # The dict needs to be manipulated, so that row_col_maker() can be used temp_part['docname'] = need_info['docname'] for col in current_needtable["columns"]: if col == "ID": temp_part['id'] = '.'.join( [need_info['id'], part['id']]) row += row_col_maker( app, fromdocname, env.needs_all_needs, temp_part, "id", make_ref=True, prefix=app.config.needs_part_prefix) elif col == "TITLE": row += row_col_maker( app, fromdocname, env.needs_all_needs, temp_part, "content", prefix=app.config.needs_part_prefix) elif col in link_type_list.keys() \ and (col == link_type_list[col]['option'].upper() + '_BACK' or col == link_type_list[col]['incoming'].upper()): row += row_col_maker( app, fromdocname, env.needs_all_needs, temp_part, link_type_list[col]['option'] + '_back', ref_lookup=True) else: row += row_col_maker(app, fromdocname, env.needs_all_needs, temp_part, col.lower()) tbody += row if len(found_needs) == 0: content.append(no_needs_found_paragraph()) # add filter information to output if current_needtable["show_filters"]: content.append(used_filter_paragraph(current_needtable)) node.replace_self(content)
def process_needtables(app, doctree, fromdocname): """ Replace all needtables nodes with a tale of filtered noded. :param app: :param doctree: :param fromdocname: :return: """ env = app.builder.env for node in doctree.traverse(Needtable): if not app.config.needs_include_needs: # Ok, this is really dirty. # If we replace a node, docutils checks, if it will not lose any attributes. # But this is here the case, because we are using the attribute "ids" of a node. # However, I do not understand, why losing an attribute is such a big deal, so we delete everything # before docutils claims about it. for att in ('ids', 'names', 'classes', 'dupnames'): node[att] = [] node.replace_self([]) continue id = node.attributes["ids"][0] current_needtable = env.need_all_needtables[id] all_needs = env.needs_all_needs if current_needtable["style"] == "" or current_needtable[ "style"].upper() not in ["TABLE", "DATATABLES"]: if app.config.needs_table_style == "": style = "DATATABLES" else: style = app.config.needs_table_style.upper() else: style = current_needtable["style"].upper() # Prepare table classes = ["NEEDS_{style}".format(style=style)] content = nodes.table(classes=classes) tgroup = nodes.tgroup() # Define Table column width # ToDo: Find a way to chosen to perfect width automatically. for col in current_needtable["columns"]: if col == "TITLE": tgroup += nodes.colspec(colwidth=15) else: tgroup += nodes.colspec(colwidth=5) node_columns = [] for col in current_needtable["columns"]: header_name = col.title() if col != "ID" else col header_name = header_name.replace("_", " ") node_columns.append( nodes.entry('', nodes.paragraph('', header_name))) tgroup += nodes.thead('', nodes.row('', *node_columns)) tbody = nodes.tbody() tgroup += tbody content += tgroup all_needs = list(all_needs.values()) if current_needtable["sort_by"] is not None: if current_needtable["sort_by"] == "id": all_needs = sorted(all_needs, key=lambda node: node["id"]) elif current_needtable["sort_by"] == "status": all_needs = sorted(all_needs, key=status_sorter) # Perform filtering of needs found_needs = procces_filters(all_needs, current_needtable) for need_info in found_needs: temp_need = need_info.copy() if temp_need['is_need']: row = nodes.row(classes=['need']) prefix = '' else: row = nodes.row(classes=['need_part']) temp_need['id'] = temp_need['id_complete'] prefix = app.config.needs_part_prefix temp_need['title'] = temp_need['content'] for col in current_needtable["columns"]: if col == "ID": row += row_col_maker(app, fromdocname, env.needs_all_needs, temp_need, "id", make_ref=True, prefix=prefix) elif col == "TITLE": row += row_col_maker(app, fromdocname, env.needs_all_needs, temp_need, "title", prefix=app.config.needs_part_prefix) elif col == "INCOMING": row += row_col_maker(app, fromdocname, env.needs_all_needs, temp_need, "links_back", ref_lookup=True) elif col == "OUTGOING": row += row_col_maker(app, fromdocname, env.needs_all_needs, temp_need, "links", ref_lookup=True) else: row += row_col_maker(app, fromdocname, env.needs_all_needs, temp_need, col.lower()) tbody += row # Need part rows if current_needtable["show_parts"] and need_info['is_need']: for key, part in need_info["parts"].items(): row = nodes.row(classes=['need_part']) temp_part = part.copy( ) # The dict needs to be manipulated, so that row_col_maker() can be used temp_part['docname'] = need_info['docname'] for col in current_needtable["columns"]: if col == "ID": temp_part['id'] = '.'.join( [need_info['id'], part['id']]) row += row_col_maker( app, fromdocname, env.needs_all_needs, temp_part, "id", make_ref=True, prefix=app.config.needs_part_prefix) elif col == "TITLE": row += row_col_maker( app, fromdocname, env.needs_all_needs, temp_part, "content", prefix=app.config.needs_part_prefix) elif col == "INCOMING": row += row_col_maker(app, fromdocname, env.needs_all_needs, temp_part, "links_back", ref_lookup=True) else: row += row_col_maker(app, fromdocname, env.needs_all_needs, temp_part, col.lower()) tbody += row if len(found_needs) == 0: content.append(no_needs_found_paragraph()) # add filter information to output if current_needtable["show_filters"]: content.append(used_filter_paragraph(current_needtable)) node.replace_self(content)