def process_needgantt(app, doctree, fromdocname): # Replace all needgantt nodes with a list of the collected needs. env = app.builder.env # link_types = env.config.needs_extra_links # allowed_link_types_options = [link.upper() for link in env.config.needs_flow_link_types] # NEEDGANTT for node in doctree.traverse(Needgantt): 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_needgantt = env.need_all_needgantts[id] all_needs_dict = env.needs_all_needs content = [] try: if "sphinxcontrib.plantuml" not in app.config.extensions: raise ImportError from sphinxcontrib.plantuml import plantuml except ImportError: no_plantuml(node) continue plantuml_block_text = ".. plantuml::\n" "\n" " @startuml" " @enduml" puml_node = plantuml(plantuml_block_text) puml_node["uml"] = "@startuml\n" # Adding config config = current_needgantt["config"] puml_node["uml"] += add_config(config) all_needs = list(all_needs_dict.values()) found_needs = process_filters(app, all_needs, current_needgantt) # Scale/timeline handling if current_needgantt["timeline"]: puml_node["uml"] += "printscale {}\n".format( current_needgantt["timeline"]) # Project start date handling start_date_string = current_needgantt["start_date"] start_date_plantuml = None if start_date_string: try: start_date = datetime.strptime(start_date_string, "%Y-%m-%d") # start_date = datetime.fromisoformat(start_date_string) # > py3.7 only except Exception: raise NeedGanttException( 'start_date "{}"for needgantt is invalid. ' 'File: {}:current_needgantt["lineno"]'.format( start_date_string, current_needgantt["docname"])) month = MONTH_NAMES[int(start_date.strftime("%m"))] start_date_plantuml = start_date.strftime(f"%dth of {month} %Y") if start_date_plantuml: puml_node["uml"] += f"Project starts the {start_date_plantuml}\n" # Element handling puml_node["uml"] += "\n' Elements definition \n\n" el_link_string = "" el_completion_string = "" el_color_string = "" for need in found_needs: complete = None if current_needgantt["milestone_filter"]: is_milestone = filter_single_need( app, need, current_needgantt["milestone_filter"]) else: is_milestone = False if current_needgantt["milestone_filter"] and is_milestone: gantt_element = "[{}] as [{}] lasts 0 days\n".format( need["title"], need["id"]) else: # Normal gantt element handling duration_option = current_needgantt["duration_option"] duration = need[duration_option] complete_option = current_needgantt["completion_option"] complete = need[complete_option] if not (duration and duration.isdigit()): logger.warning( "Duration not set or invalid for needgantt chart. " "Need: {}. Duration: {}".format(need["id"], duration)) duration = 1 gantt_element = "[{}] as [{}] lasts {} days\n".format( need["title"], need["id"], duration) el_link_string += "[{}] links to [[{}]]\n".format( need["title"], calculate_link(app, need, fromdocname)) if complete: complete = complete.replace("%", "") el_completion_string += "[{}] is {}% completed\n".format( need["title"], complete) el_color_string += "[{}] is colored in {}\n".format( need["title"], need["type_color"]) puml_node["uml"] += gantt_element puml_node["uml"] += "\n' Element links definition \n\n" puml_node[ "uml"] += "\n' Deactivated, as currently supported by plantuml beta only" # ToDo: Activate if linking is working with default plantuml # puml_node["uml"] += el_link_string + '\n' puml_node["uml"] += "\n' Element completion definition \n\n" puml_node["uml"] += el_completion_string + "\n" puml_node["uml"] += "\n' Element color definition \n\n" if current_needgantt["no_color"]: puml_node["uml"] += "' Color support deactivated via flag" else: puml_node["uml"] += el_color_string + "\n" # Constrain handling puml_node["uml"] += "\n' Constraints definition \n\n" puml_node["uml"] += "\n' Constraints definition \n\n" for need in found_needs: if current_needgantt["milestone_filter"]: is_milestone = filter_single_need( app, need, current_needgantt["milestone_filter"]) else: is_milestone = False constrain_types = [ "starts_with_links", "starts_after_links", "ends_with_links" ] for con_type in constrain_types: if is_milestone: keyword = "happens" elif con_type in ["starts_with_links", "starts_after_links"]: keyword = "starts" else: keyword = "ends" if con_type in ["starts_after_links", "ends_with_links"]: start_end_sync = "end" else: start_end_sync = "start" for link_type in current_needgantt[con_type]: start_with_links = need[link_type] for start_with_link in start_with_links: start_need = all_needs_dict[start_with_link] gantt_constraint = "[{}] {} at [{}]'s " "{}\n".format( need["id"], keyword, start_need["id"], start_end_sync) puml_node["uml"] += gantt_constraint # Create a legend if current_needgantt["show_legend"]: puml_node["uml"] += create_legend(app.config.needs_types) puml_node["uml"] += "\n@enduml" puml_node["incdir"] = os.path.dirname(current_needgantt["docname"]) puml_node["filename"] = os.path.split( current_needgantt["docname"])[1] # Needed for plantuml >= 0.9 scale = int(current_needgantt["scale"]) # if scale != 100: puml_node["scale"] = scale puml_node = nodes.figure("", puml_node) puml_node["align"] = current_needgantt["align"] or "center" if current_needgantt["caption"]: # Make the caption to a link to the original file. try: if "SVG" in app.config.plantuml_output_format.upper(): file_ext = "svg" else: file_ext = "png" except Exception: file_ext = "png" gen_flow_link = generate_name(app, puml_node.children[0], file_ext) current_file_parts = fromdocname.split("/") subfolder_amount = len(current_file_parts) - 1 img_location = "../" * subfolder_amount + "_images/" + gen_flow_link[ 0].split("/")[-1] flow_ref = nodes.reference("t", current_needgantt["caption"], refuri=img_location) puml_node += nodes.caption("", "", flow_ref) # Add lineno to node puml_node.line = current_needgantt["lineno"] content.append(puml_node) if len(content) == 0: nothing_found = "No needs passed the filters" para = nodes.paragraph() nothing_found_node = nodes.Text(nothing_found, nothing_found) para += nothing_found_node content.append(para) if current_needgantt["show_filters"]: content.append(get_filter_para(current_needgantt)) if current_needgantt["debug"]: content += get_debug_container(puml_node) puml_node["class"] = ["needgantt"] node.replace_self(content)
def process_needsequence(app, doctree, fromdocname): # Replace all needsequence nodes with a list of the collected needs. env = app.builder.env link_types = env.config.needs_extra_links # allowed_link_types_options = [link.upper() for link in env.config.needs_flow_link_types] # NEEDSEQUENCE for node in doctree.traverse(Needsequence): 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_needsequence = env.need_all_needsequences[id] all_needs_dict = env.needs_all_needs option_link_types = [ link.upper() for link in current_needsequence["link_types"] ] for lt in option_link_types: if lt not in [link["option"].upper() for link in link_types]: logger.warning( "Unknown link type {link_type} in needsequence {flow}. Allowed values:" " {link_types}".format( link_type=lt, flow=current_needsequence["target_node"], link_types=",".join(link_types))) content = [] try: if "sphinxcontrib.plantuml" not in app.config.extensions: raise ImportError from sphinxcontrib.plantuml import plantuml except ImportError: no_plantuml(node) continue plantuml_block_text = ".. plantuml::\n" "\n" " @startuml" " @enduml" puml_node = plantuml(plantuml_block_text) puml_node["uml"] = "@startuml\n" # Adding config config = current_needsequence["config"] puml_node["uml"] += add_config(config) # all_needs = list(all_needs_dict.values()) start_needs_id = [ x.strip() for x in re.split(";|,", current_needsequence["start"]) ] if len(start_needs_id) == 0: raise NeedsequenceDirective("No start-id set for needsequence" " File {}" ":{}".format( {current_needsequence["docname"]}, current_needsequence["lineno"])) puml_node["uml"] += "\n' Nodes definition \n\n" # Add start participants p_string = "" c_string = "" for need_id in start_needs_id: try: need = all_needs_dict[need_id.strip()] except KeyError: raise NeedSequenceException( "Given {} in needsequence unknown." " File {}" ":{}".format(need_id, current_needsequence["docname"], current_needsequence["lineno"])) # Add children of participants _msg_receiver_needs, p_string_new, c_string_new = get_message_needs( app, need, current_needsequence["link_types"], all_needs_dict, filter=current_needsequence["filter"]) p_string += p_string_new c_string += c_string_new puml_node["uml"] += p_string puml_node["uml"] += "\n' Connection definition \n\n" puml_node["uml"] += c_string # Create a legend if current_needsequence["show_legend"]: puml_node["uml"] += create_legend(app.config.needs_types) puml_node["uml"] += "\n@enduml" puml_node["incdir"] = os.path.dirname(current_needsequence["docname"]) puml_node["filename"] = os.path.split( current_needsequence["docname"])[1] # Needed for plantuml >= 0.9 scale = int(current_needsequence["scale"]) # if scale != 100: puml_node["scale"] = scale puml_node = nodes.figure("", puml_node) if current_needsequence["align"]: puml_node["align"] = current_needsequence["align"] else: puml_node["align"] = "center" if current_needsequence["caption"]: # Make the caption to a link to the original file. try: if "SVG" in app.config.plantuml_output_format.upper(): file_ext = "svg" else: file_ext = "png" except Exception: file_ext = "png" gen_flow_link = generate_name(app, puml_node.children[0], file_ext) current_file_parts = fromdocname.split("/") subfolder_amount = len(current_file_parts) - 1 img_locaton = "../" * subfolder_amount + "_images/" + gen_flow_link[ 0].split("/")[-1] flow_ref = nodes.reference("t", current_needsequence["caption"], refuri=img_locaton) puml_node += nodes.caption("", "", flow_ref) # Add lineno to node puml_node.line = current_needsequence["lineno"] content.append(puml_node) if len(content) == 0: nothing_found = "No needs passed the filters" para = nodes.paragraph() nothing_found_node = nodes.Text(nothing_found, nothing_found) para += nothing_found_node content.append(para) if current_needsequence["show_filters"]: content.append(get_filter_para(current_needsequence)) if current_needsequence["debug"]: content += get_debug_container(puml_node) node.replace_self(content)
def process_needsequence(app, doctree, fromdocname): # Replace all needsequence nodes with a list of the collected needs. env = app.builder.env link_types = env.config.needs_extra_links # allowed_link_types_options = [link.upper() for link in env.config.needs_flow_link_types] # NEEDSEQUENCE for node in doctree.traverse(Needsequence): 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_needsequence = env.need_all_needsequences[id] all_needs_dict = env.needs_all_needs option_link_types = [link.upper() for link in current_needsequence['link_types']] for lt in option_link_types: if lt not in [link['option'].upper() for link in link_types]: logger.warning('Unknown link type {link_type} in needsequence {flow}. Allowed values:' ' {link_types}'.format(link_type=lt, flow=current_needsequence['target_node'], link_types=",".join(link_types) )) content = [] try: if "sphinxcontrib.plantuml" not in app.config.extensions: raise ImportError from sphinxcontrib.plantuml import plantuml except ImportError: no_plantuml(node) continue plantuml_block_text = ".. plantuml::\n" \ "\n" \ " @startuml" \ " @enduml" puml_node = plantuml(plantuml_block_text, **dict()) puml_node["uml"] = "@startuml\n" # Adding config config = current_needsequence['config'] puml_node["uml"] += add_config(config) # all_needs = list(all_needs_dict.values()) start_needs_id = [x.strip() for x in re.split(";|,", current_needsequence['start'])] if len(start_needs_id) == 0: raise NeedsequenceDirective('No start-id set for needsequence' ' File {}' ':{}'.format({current_needsequence["docname"]}, current_needsequence["lineno"])) puml_node["uml"] += '\n\' Nodes definition \n\n' # Add start participants p_string = '' c_string = '' for need_id in start_needs_id: try: need = all_needs_dict[need_id.strip()] except KeyError: raise NeedSequenceException('Given {} in needsequence unknown.' ' File {}' ':{}'.format(need_id, current_needsequence["docname"], current_needsequence["lineno"])) # Add children of participants msg_receiver_needs, p_string_new, c_string_new = get_message_needs(need, current_needsequence['link_types'], all_needs_dict, filter=current_needsequence['filter']) p_string += p_string_new c_string += c_string_new puml_node["uml"] += p_string puml_node["uml"] += '\n\' Connection definition \n\n' puml_node["uml"] += c_string # Create a legend if current_needsequence["show_legend"]: puml_node["uml"] += '\n\n\' Legend definition \n\n' 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"] += "\n@enduml" puml_node["incdir"] = os.path.dirname(current_needsequence["docname"]) puml_node["filename"] = os.path.split(current_needsequence["docname"])[1] # Needed for plantuml >= 0.9 scale = int(current_needsequence['scale']) # if scale != 100: puml_node['scale'] = scale puml_node = nodes.figure('', puml_node) if current_needsequence['align'] is not None and len(current_needsequence['align']) != '': puml_node['align'] = current_needsequence['align'] else: puml_node['align'] = 'center' if current_needsequence['caption'] is not None and len(current_needsequence['caption']) != '': # Make the caption to a link to the original file. try: if "SVG" in app.config.plantuml_output_format.upper(): file_ext = 'svg' else: file_ext = 'png' except Exception: file_ext = 'png' gen_flow_link = generate_name(app, puml_node.children[0], file_ext) current_file_parts = fromdocname.split('/') subfolder_amount = len(current_file_parts) - 1 img_locaton = '../' * subfolder_amount + '_images/' + gen_flow_link[0].split('/')[-1] flow_ref = nodes.reference('t', current_needsequence['caption'], refuri=img_locaton) puml_node += nodes.caption('', '', flow_ref) content.append(puml_node) if len(content) == 0: nothing_found = "No needs passed the filters" para = nodes.paragraph() nothing_found_node = nodes.Text(nothing_found, nothing_found) para += nothing_found_node content.append(para) if current_needsequence["show_filters"]: content.append(get_filter_para(current_needsequence)) if current_needsequence['debug']: content += get_debug_container(puml_node) node.replace_self(content)
def process_needgantt(app, doctree, fromdocname): # Replace all needgantt nodes with a list of the collected needs. env = app.builder.env # link_types = env.config.needs_extra_links # allowed_link_types_options = [link.upper() for link in env.config.needs_flow_link_types] # NEEDGANTT for node in doctree.traverse(Needgantt): 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_needgantt = env.need_all_needgantts[id] all_needs_dict = env.needs_all_needs content = [] try: if "sphinxcontrib.plantuml" not in app.config.extensions: raise ImportError from sphinxcontrib.plantuml import plantuml except ImportError: no_plantuml(node) continue plantuml_block_text = ".. plantuml::\n" \ "\n" \ " @startuml" \ " @enduml" puml_node = plantuml(plantuml_block_text, **dict()) puml_node["uml"] = "@startuml\n" # Adding config config = current_needgantt['config'] puml_node["uml"] += add_config(config) all_needs = list(all_needs_dict.values()) found_needs = process_filters(all_needs, current_needgantt) # Scale/timeline handling if current_needgantt['timeline'] is not None and current_needgantt[ 'timeline'] != '': puml_node["uml"] += 'printscale {}\n'.format( current_needgantt["timeline"]) # Project start date handling start_date_string = current_needgantt['start_date'] start_date_plantuml = None if start_date_string is not None and start_date_string != '': try: start_date = datetime.strptime(start_date_string, '%Y-%m-%d') # start_date = datetime.fromisoformat(start_date_string) # > py3.7 only except Exception: raise NeedGanttException( 'start_date "{}"for needgantt is invalid. ' 'File: {}:current_needgantt["lineno"]'.format( start_date_string, current_needgantt["docname"])) month = MONTH_NAMES[int(start_date.strftime("%-m"))] start_date_plantuml = start_date.strftime( "%dth of {} %Y".format(month)) if start_date_plantuml is not None: puml_node["uml"] += 'Project starts the {}\n'.format( start_date_plantuml) # Element handling puml_node["uml"] += '\n\' Elements definition \n\n' el_link_string = '' el_completion_string = '' el_color_string = '' for need in found_needs: complete = None if current_needgantt[ 'milestone_filter'] is None or current_needgantt[ 'milestone_filter'] == '': is_milestone = False else: is_milestone = filter_single_need( need, current_needgantt['milestone_filter']) if current_needgantt['milestone_filter'] is None or current_needgantt['milestone_filter'] == '' or \ not is_milestone: # Normal gantt element handling duration_option = current_needgantt['duration_option'] duration = need[duration_option] complete_option = current_needgantt['completion_option'] complete = need[complete_option] if duration is None or duration == '' or not duration.isdigit( ): logger.warning( 'Duration not set or invalid for needgantt chart. ' 'Need: {}. Duration: {}'.format(need["id"], duration)) duration = 1 gantt_element = '[{}] as [{}] lasts {} days\n'.format( need["title"], need["id"], duration) else: gantt_element = '[{}] as [{}] lasts 0 days\n'.format( need["title"], need["id"]) el_link_string += '[{}] links to [[{}]]\n'.format( need["title"], calculate_link(app, need)) if complete is not None and complete != '': complete = complete.replace('%', '') el_completion_string += '[{}] is {}% completed\n'.format( need["title"], complete) el_color_string += '[{}] is colored in {}\n'.format( need["title"], need["type_color"]) puml_node["uml"] += gantt_element puml_node["uml"] += '\n\' Element links definition \n\n' puml_node[ "uml"] += '\n\' Deactivated, as currently supported by plantuml beta only' # ToDo: Activate if linking is working with default plantuml # puml_node["uml"] += el_link_string + '\n' puml_node["uml"] += '\n\' Element completion definition \n\n' puml_node["uml"] += el_completion_string + '\n' puml_node["uml"] += '\n\' Element color definition \n\n' if current_needgantt['no_color']: puml_node["uml"] += '\' Color support deactivated via flag' else: puml_node["uml"] += el_color_string + '\n' # Constrain handling puml_node["uml"] += '\n\' Constraints definition \n\n' puml_node["uml"] += '\n\' Constraints definition \n\n' for need in found_needs: if current_needgantt[ 'milestone_filter'] is None or current_needgantt[ 'milestone_filter'] == '': is_milestone = False else: is_milestone = filter_single_need( need, current_needgantt['milestone_filter']) constrain_types = [ 'starts_with_links', 'starts_after_links', 'ends_with_links' ] for con_type in constrain_types: if is_milestone: keyword = 'happens' elif con_type in ['starts_with_links', 'starts_after_links']: keyword = 'starts' else: keyword = 'ends' if con_type in ['starts_after_links', 'ends_with_links']: start_end_sync = 'end' else: start_end_sync = 'start' for link_type in current_needgantt[con_type]: start_with_links = need[link_type] for start_with_link in start_with_links: start_need = all_needs_dict[start_with_link] gantt_constraint = '[{}] {} at [{}]\'s ' \ '{}\n'.format(need["id"], keyword, start_need["id"], start_end_sync) puml_node["uml"] += gantt_constraint # Create a legend if current_needgantt["show_legend"]: puml_node["uml"] += '\n\n\' Legend definition \n\n' 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"] += "\n@enduml" puml_node["incdir"] = os.path.dirname(current_needgantt["docname"]) puml_node["filename"] = os.path.split( current_needgantt["docname"])[1] # Needed for plantuml >= 0.9 scale = int(current_needgantt['scale']) # if scale != 100: puml_node['scale'] = scale puml_node = nodes.figure('', puml_node) puml_node['align'] = current_needgantt['align'] or 'center' if current_needgantt['caption']: # Make the caption to a link to the original file. try: if "SVG" in app.config.plantuml_output_format.upper(): file_ext = 'svg' else: file_ext = 'png' except Exception: file_ext = 'png' gen_flow_link = generate_name(app, puml_node.children[0], file_ext) current_file_parts = fromdocname.split('/') subfolder_amount = len(current_file_parts) - 1 img_location = '../' * subfolder_amount + '_images/' + gen_flow_link[ 0].split('/')[-1] flow_ref = nodes.reference('t', current_needgantt['caption'], refuri=img_location) puml_node += nodes.caption('', '', flow_ref) content.append(puml_node) if len(content) == 0: nothing_found = "No needs passed the filters" para = nodes.paragraph() nothing_found_node = nodes.Text(nothing_found, nothing_found) para += nothing_found_node content.append(para) if current_needgantt["show_filters"]: content.append(get_filter_para(current_needgantt)) if current_needgantt['debug']: content += get_debug_container(puml_node) puml_node['class'] = ['needgantt'] node.replace_self(content)