def add_need(app, state, docname, lineno, need_type, title, id=None, content="", status=None, tags=None, links_string=None, hide=False, hide_tags=False, hide_status=False, collapse=None, style=None, layout=None, template=None, pre_template=None, post_template=None, **kwargs): """ Creates a new need and returns its node. ``add_need`` allows to create needs programmatically and use its returned node to be integrated in any docutils based structure. ``kwags`` can contain options defined in ``needs_extra_options`` and ``needs_extra_links``. If an entry is found in ``kwags``, which *is not* specified in the configuration or registered e.g. via ``add_extra_option``, an exception is raised. **Usage**: Normally needs get created during handling of a specialised directive. So this pseudo-code shows how to use ``add_need`` inside such a directive. .. code-block:: python from docutils.parsers.rst import Directive from sphinxcontrib.needs.api import add_need class MyDirective(Directive) # configs and init routine def run(): main_section = [] docname = self.state.document.settings.env.docname # All needed sphinx-internal information we can take from our current directive class. # e..g app, state, lineno main_section += add_need(self.env.app, self.state, docname, self.lineno, need_type="req", title="my title", id="ID_001" content=self.content) # Feel free to add custom stuff to main_section like sections, text, ... return main_section :param app: Sphinx application object. :param state: Current state object. :param docname: documentation name. :param lineno: line number. :param need_type: Name of the need type to create. :param title: String as title. :param id: ID as string. If not given, a id will get generated. :param content: Content as single string. :param status: Status as string. :param tags: Tags as single string. :param links_string: Links as single string. :param hide: boolean value. :param hide_tags: boolean value. (Not used with Sphinx-Needs >0.5.0) :param hide_status: boolean value. (Not used with Sphinx-Needs >0.5.0) :param collapse: boolean value. :param style: String value of class attribute of node. :param layout: String value of layout definition to use :param template: Template name to use for the content of this need :param pre_template: Template name to use for content added before need :param post_template: Template name to use for the content added after need :return: node """ ############################################################################################# # Get environment ############################################################################################# env = app.env types = env.app.config.needs_types type_name = "" type_prefix = "" type_color = "" type_style = "" found = False for ntype in types: if ntype["directive"] == need_type: type_name = ntype["title"] type_prefix = ntype["prefix"] type_color = ntype["color"] type_style = ntype["style"] found = True break if not found: # This should never happen. But it may happen, if Sphinx is called multiples times # inside one ongoing python process. # In this case the configuration from a prior sphinx run may be active, which has registered a directive, # which is reused inside a current document, but no type was defined for the current run... # Yeah, this really has happened... return [nodes.Text('', '')] # Get the id or generate a random string/hash string, which is hopefully unique # TODO: Check, if id was already given. If True, recalculate id # id = self.options.get("id", ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for # _ in range(5))) if id is None and env.app.config.needs_id_required: raise NeedsNoIdException( "An id is missing for this need and must be set, because 'needs_id_required' " "is set to True in conf.py. Need '{}' in {} ({})".format( title, docname, lineno)) if id is None: need_id = make_hashed_id(app, need_type, title, content) else: need_id = id if env.app.config.needs_id_regex and not re.match( env.app.config.needs_id_regex, need_id): raise NeedsInvalidException( "Given ID '{id}' does not match configured regex '{regex}'".format( id=need_id, regex=env.app.config.needs_id_regex)) #print ("create need: ", need_id, id) # Calculate target id, to be able to set a link back target_node = nodes.target('', '', ids=[need_id]) #print (target_node) # Removed 0.5.0 # if collapse is None: # collapse = getattr(env.app.config, "needs_collapse_details", True) # Handle status # Check if status is in needs_statuses. If not raise an error. if env.app.config.needs_statuses: if status not in [ stat["name"] for stat in env.app.config.needs_statuses ]: raise NeedsStatusNotAllowed( "Status {0} of need id {1} is not allowed " "by config value 'needs_statuses'.".format(status, need_id)) if tags is None: tags = [] if len(tags) > 0: tags = [tag.strip() for tag in re.split(";|,", tags)] for i in range(len(tags)): if len(tags[i]) == 0 or tags[i].isspace(): del (tags[i]) logger.warning( 'Scruffy tag definition found in need {}. ' 'Defined tag contains spaces only.'.format(need_id)) # Check if tag is in needs_tags. If not raise an error. if env.app.config.needs_tags: for tag in tags: if tag not in [ tag["name"] for tag in env.app.config.needs_tags ]: raise NeedsTagNotAllowed( "Tag {0} of need id {1} is not allowed " "by config value 'needs_tags'.".format(tag, need_id)) # This may have cut also dynamic function strings, as they can contain , as well. # So let put them together again # ToDo: There may be a smart regex for the splitting. This would avoid this mess of code... tags = _fix_list_dyn_func(tags) ############################################################################################# # Add need to global need list ############################################################################################# # be sure, global var is available. If not, create it if not hasattr(env, 'needs_all_needs'): env.needs_all_needs = {} if need_id in env.needs_all_needs.keys(): if id is not None: raise NeedsDuplicatedId( "A need with ID {} already exists! " "This is not allowed. Document {}[{}] Title: {}.".format( need_id, docname, lineno, title)) else: # this is a generated ID raise NeedsDuplicatedId( "Needs could not generate a unique ID for a need with " "the title '{}' because another need had the same title. " "Either supply IDs for the requirements or ensure the " "titles are different. NOTE: If title is being generated " "from the content, then ensure the first sentence of the " "requirements are different.".format(' '.join(title))) # Trim title if it is too long max_length = getattr(env.app.config, 'needs_max_title_length', 30) if max_length == -1 or len(title) <= max_length: trimmed_title = title elif max_length <= 3: trimmed_title = title[:max_length] else: trimmed_title = title[:max_length - 3] + '...' # Add the need and all needed information needs_info = { 'docname': docname, 'lineno': lineno, 'target_node': target_node, 'content_node': None, # gets set after rst parsing 'type': need_type, 'type_name': type_name, 'type_prefix': type_prefix, 'type_color': type_color, 'type_style': type_style, 'status': status, 'tags': tags, 'id': need_id, 'title': trimmed_title, 'full_title': title, 'content': content, 'collapse': collapse, 'style': style, 'layout': layout, 'template': template, 'pre_template': pre_template, 'post_template': post_template, 'hide': hide, 'parts': {}, 'is_part': False, 'is_need': True } #print (needs_info) needs_extra_options = env.config.needs_extra_options.keys() _merge_extra_options(needs_info, kwargs, needs_extra_options) needs_global_options = env.config.needs_global_options _merge_global_options(needs_info, needs_global_options) link_names = [x['option'] for x in env.config.needs_extra_links] for keyword in kwargs: if keyword not in needs_extra_options and keyword not in link_names: raise NeedsInvalidOption( 'Unknown Option {}. ' 'Use needs_extra_options or needs_extra_links in conf.py' 'to define this option.'.format(keyword)) # Merge links copy_links = [] for link_type in env.config.needs_extra_links: # Check, if specific link-type got some arguments during method call if link_type['option'] not in list(kwargs.keys( )) and link_type['option'] not in needs_global_options.keys(): # if not we set no links, but entry in needS_info must be there links = [] elif link_type['option'] in needs_global_options.keys() and \ (link_type['option'] not in list(kwargs.keys()) or len(str( kwargs[link_type['option']])) == 0): # If it is in global option, value got already set during prior handling of them links_string = needs_info[link_type['option']] links = _read_in_links(links_string) else: # if it is set in kwargs, take this value and maybe override set value from global_options links_string = kwargs[link_type['option']] links = _read_in_links(links_string) needs_info[link_type["option"]] = links needs_info['{}_back'.format(link_type["option"])] = set() if 'copy' not in link_type.keys(): link_type['copy'] = False if link_type['copy'] and link_type['option'] != 'links': copy_links += links # Save extra links for main-links needs_info['links'] += copy_links # Set copied links to main-links env.needs_all_needs[need_id] = needs_info # Template builds ############################## # template if needs_info['template'] is not None and len(needs_info['template']) > 0: new_content = _prepare_template(app, needs_info, 'template') # Overwrite current content content = new_content needs_info['content'] = new_content else: new_content = None # pre_template if needs_info['pre_template'] is not None and len( needs_info['pre_template']) > 0: pre_content = _prepare_template(app, needs_info, 'pre_template') needs_info['pre_content'] = pre_content else: pre_content = None # post_template if needs_info['post_template'] is not None and len( needs_info['post_template']) > 0: post_content = _prepare_template(app, needs_info, 'post_template') needs_info['post_content'] = post_content else: post_content = None if needs_info['hide']: return [target_node] # Adding of basic Need node. ############################ # Title and meta data information gets added alter during event handling via process_need_nodes() # We just add a basic need node and render the rst-based content, because this can not be done later. # style_classes = ['need', type_name, 'need-{}'.format(type_name.lower())] # Used < 0.4.4 style_classes = ['need', 'need-{}'.format(need_type.lower())] if style is not None and style != '': style_classes.append(style) node_need = sphinxcontrib.needs.directives.need.Need('', classes=style_classes, ids=[need_id]) # Render rst-based content and add it to the need-node node_need_content = _render_template(content, docname, lineno, state) need_parts = find_parts(node_need_content) update_need_with_parts(env, needs_info, need_parts) node_need += node_need_content.children needs_info['content_node'] = node_need #print("target_node: ", target_node) #print("node_need: ", node_need) #needs_info['content_node']['id'] = need_id return_nodes = [target_node] + [node_need] if pre_content is not None: node_need_pre_content = _render_template(pre_content, docname, lineno, state) pre_container = nodes.container() pre_container += node_need_pre_content.children return_nodes = [pre_container] + return_nodes if post_content is not None: node_need_post_content = _render_template(post_content, docname, lineno, state) post_container = nodes.container() post_container += node_need_post_content.children return_nodes = return_nodes + [post_container] return return_nodes
def run(self): ############################################################################################# # Get environment ############################################################################################# env = self.env types = env.app.config.needs_types type_name = "" type_prefix = "" type_color = "" type_style = "" found = False for type in types: if type["directive"] == self.name: type_name = type["title"] type_prefix = type["prefix"] type_color = type["color"] type_style = type["style"] found = True break if not found: # This should never happen. But it may happen, if Sphinx is called multiples times # inside one ongoing python process. # In this case the configuration from a prior sphinx run may be active, which has registered a directive, # which is reused inside a current document, but no type was defined for the current run... # Yeah, this really has happened... return [nodes.Text('', '')] # Get the id or generate a random string/hash string, which is hopefully unique # TODO: Check, if id was already given. If True, recalculate id # id = self.options.get("id", ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for # _ in range(5))) if "id" not in self.options.keys( ) and env.app.config.needs_id_required: raise NeedsNoIdException( "An id is missing for this need and must be set, because 'needs_id_required' " "is set to True in conf.py") id = self.options.get( "id", self.make_hashed_id(type_prefix, env.config.needs_id_length)) if env.app.config.needs_id_regex and not re.match( env.app.config.needs_id_regex, id): raise NeedsInvalidException( "Given ID '{id}' does not match configured regex '{regex}'". format(id=id, regex=env.app.config.needs_id_regex)) # Calculate target id, to be able to set a link back target_node = nodes.target('', '', ids=[id]) collapse = str(self.options.get("collapse", "")) if isinstance(collapse, str) and len(collapse) > 0: if collapse.upper() in ["TRUE", 1, "YES"]: collapse = True elif collapse.upper() in ["FALSE", 0, "NO"]: collapse = False else: raise Exception("collapse attribute must be true or false") else: collapse = getattr(env.app.config, "needs_collapse_details", True) hide = True if "hide" in self.options.keys() else False hide_tags = True if "hide_tags" in self.options.keys() else False hide_status = True if "hide_status" in self.options.keys() else False content = "\n".join(self.content) # Handle status status = self.options.get("status", None) # Check if status is in needs_statuses. If not raise an error. if env.app.config.needs_statuses: if status not in [ stat["name"] for stat in env.app.config.needs_statuses ]: raise NeedsStatusNotAllowed( "Status {0} of need id {1} is not allowed " "by config value 'needs_statuses'.".format(status, id)) tags = self.options.get("tags", []) if len(tags) > 0: # Not working regex. # test = re.match(r'^(?: *(\[\[.*?\]\]|[^,; ]+) *(?:,|;|$)?)+', tags) tags = [tag.strip() for tag in re.split(";|,", tags)] for i in range(len(tags)): if len(tags[i]) == 0 or tags[i].isspace(): del (tags[i]) logger.warning( 'Scruffy tag definition found in need {}. ' 'Defined tag contains spaces only.'.format(id)) # Check if tag is in needs_tags. If not raise an error. if env.app.config.needs_tags: for tag in tags: if tag not in [ tag["name"] for tag in env.app.config.needs_tags ]: raise NeedsTagNotAllowed( "Tag {0} of need id {1} is not allowed " "by config value 'needs_tags'.".format(tag, id)) # This may have cut also dynamic function strings, as they can contain , as well. # So let put them together again # ToDo: There may be a smart regex for the splitting. This would avoid this mess of code... tags = _fix_list_dyn_func(tags) # Get links links_string = self.options.get("links", []) links = [] if len(links_string) > 0: # links = [link.strip() for link in re.split(";|,", links) if not link.isspace()] for link in re.split(";|,", links_string): if not link.isspace(): links.append(link.strip()) else: logger.warning( 'Grubby link definition found in need {}. ' 'Defined link contains spaces only.'.format(id)) # This may have cut also dynamic function strings, as they can contain , as well. # So let put them together again # ToDo: There may be a smart regex for the splitting. This would avoid this mess of code... links = _fix_list_dyn_func(links) ############################################################################################# # Add need to global need list ############################################################################################# # be sure, global var is available. If not, create it if not hasattr(env, 'needs_all_needs'): env.needs_all_needs = {} if id in env.needs_all_needs.keys(): if 'id' in self.options: raise NeedsDuplicatedId( "A need with ID {} already exists! " "This is not allowed. Document {}[{}]".format( id, self.docname, self.lineno)) else: # this is a generated ID raise NeedsDuplicatedId( "Needs could not generate a unique ID for a need with " "the title '{}' because another need had the same title. " "Either supply IDs for the requirements or ensure the " "titles are different. NOTE: If title is being generated " "from the content, then ensure the first sentence of the " "requirements are different.".format(' '.join( self.full_title))) # Add the need and all needed information needs_info = { 'docname': self.docname, 'lineno': self.lineno, 'links_back': set(), 'target_node': target_node, 'type': self.name, 'type_name': type_name, 'type_prefix': type_prefix, 'type_color': type_color, 'type_style': type_style, 'status': status, 'tags': tags, 'id': id, 'links': links, 'title': self.trimmed_title, 'full_title': self.full_title, 'content': content, 'collapse': collapse, 'hide': hide, 'hide_tags': hide_tags, 'hide_status': hide_status, 'parts': {}, 'is_part': False, 'is_need': True } self.merge_extra_options(needs_info) self.merge_global_options(needs_info) env.needs_all_needs[id] = needs_info if hide: return [target_node] # Adding of basic Need node. ############################ # Title and meta data information gets added alter during event handling via process_need_nodes() # We just add a basic need node and render the rst-based content, because this can not be done later. node_need = Need( '', classes=['need', self.name, 'need-{}'.format(type_name.lower())]) # Render rst-based content and add it to the need-node rst = ViewList() for line in self.content: rst.append(line, self.docname, self.lineno) node_need_content = nodes.Element() node_need_content.document = self.state.document nested_parse_with_titles(self.state, rst, node_need_content) need_parts = find_parts(node_need_content) update_need_with_parts(env, needs_info, need_parts) node_need += node_need_content.children return [target_node] + [node_need]
def add_need( app, state, docname, lineno, need_type, title, id=None, content="", status=None, tags=None, links_string=None, hide=False, hide_tags=False, hide_status=False, collapse=None, style=None, layout=None, template=None, pre_template=None, post_template=None, is_external=False, external_url=None, external_css="external_link", **kwargs, ): """ Creates a new need and returns its node. ``add_need`` allows to create needs programmatically and use its returned node to be integrated in any docutils based structure. ``kwags`` can contain options defined in ``needs_extra_options`` and ``needs_extra_links``. If an entry is found in ``kwags``, which *is not* specified in the configuration or registered e.g. via ``add_extra_option``, an exception is raised. If ``is_external`` is set to ``True``, no node will be created. Instead the need is referencing an external url. Used mostly for :ref:`needs_external_needs` to integrate and reference needs from external documentation. **Usage**: Normally needs get created during handling of a specialised directive. So this pseudo-code shows how to use ``add_need`` inside such a directive. .. code-block:: python from docutils.parsers.rst import Directive from sphinxcontrib.needs.api import add_need class MyDirective(Directive) # configs and init routine def run(): main_section = [] docname = self.state.document.settings.env.docname # All needed sphinx-internal information we can take from our current directive class. # e..g app, state, lineno main_section += add_need(self.env.app, self.state, docname, self.lineno, need_type="req", title="my title", id="ID_001" content=self.content) # Feel free to add custom stuff to main_section like sections, text, ... return main_section :param app: Sphinx application object. :param state: Current state object. :param docname: documentation name. :param lineno: line number. :param need_type: Name of the need type to create. :param title: String as title. :param id: ID as string. If not given, a id will get generated. :param content: Content as single string. :param status: Status as string. :param tags: Tags as single string. :param links_string: Links as single string. :param hide: boolean value. :param hide_tags: boolean value. (Not used with Sphinx-Needs >0.5.0) :param hide_status: boolean value. (Not used with Sphinx-Needs >0.5.0) :param collapse: boolean value. :param style: String value of class attribute of node. :param layout: String value of layout definition to use :param template: Template name to use for the content of this need :param pre_template: Template name to use for content added before need :param post_template: Template name to use for the content added after need :param is_external: Is true, no node is created and need is referencing external url :param external_url: URL as string, which is used as target if ``is_external`` is ``True`` :param external_css: CSS class name as string, which is set for the <a> tag. :return: node """ ############################################################################################# # Get environment ############################################################################################# env = app.env types = env.app.config.needs_types type_name = "" type_prefix = "" type_color = "" type_style = "" found = False for ntype in types: if ntype["directive"] == need_type: type_name = ntype["title"] type_prefix = ntype["prefix"] type_color = ntype[ "color"] or "#000000" # if no color set up user in config type_style = ntype[ "style"] or "node" # if no style set up user in config found = True break if not found: # This should never happen. But it may happen, if Sphinx is called multiples times # inside one ongoing python process. # In this case the configuration from a prior sphinx run may be active, which has registered a directive, # which is reused inside a current document, but no type was defined for the current run... # Yeah, this really has happened... return [nodes.Text("", "")] # Get the id or generate a random string/hash string, which is hopefully unique # TODO: Check, if id was already given. If True, recalculate id # id = self.options.get("id", ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for # _ in range(5))) if id is None and env.app.config.needs_id_required: raise NeedsNoIdException( "An id is missing for this need and must be set, because 'needs_id_required' " "is set to True in conf.py. Need '{}' in {} ({})".format( title, docname, lineno)) if id is None: need_id = make_hashed_id(app, need_type, title, content) else: need_id = id if env.app.config.needs_id_regex and not re.match( env.app.config.needs_id_regex, need_id): raise NeedsInvalidException( "Given ID '{id}' does not match configured regex '{regex}'".format( id=need_id, regex=env.app.config.needs_id_regex)) # Calculate target id, to be able to set a link back if is_external: target_node = None external_url = external_url else: target_node = nodes.target("", "", ids=[need_id], refid=need_id) external_url = None # Handle status # Check if status is in needs_statuses. If not raise an error. if env.app.config.needs_statuses and status not in [ stat["name"] for stat in env.app.config.needs_statuses ]: raise NeedsStatusNotAllowed( f"Status {status} of need id {need_id} is not allowed " "by config value 'needs_statuses'.") if tags is None: tags = [] if len(tags) > 0: # tags should be a string, but it can also be already a list,which can be used. if isinstance(tags, str): tags = [tag.strip() for tag in re.split(";|,", tags)] new_tags = [] # Shall contain only valid tags for i in range(len(tags)): if len(tags[i]) == 0 or tags[i].isspace(): logger.warning( f"Scruffy tag definition found in need {need_id}. " "Defined tag contains spaces only.") else: new_tags.append(tags[i]) tags = new_tags # Check if tag is in needs_tags. If not raise an error. if env.app.config.needs_tags: for tag in tags: if tag not in [ tag["name"] for tag in env.app.config.needs_tags ]: raise NeedsTagNotAllowed( f"Tag {tag} of need id {need_id} is not allowed " "by config value 'needs_tags'.") # This may have cut also dynamic function strings, as they can contain , as well. # So let put them together again # ToDo: There may be a smart regex for the splitting. This would avoid this mess of code... tags = _fix_list_dyn_func(tags) ############################################################################################# # Add need to global need list ############################################################################################# # be sure, global var is available. If not, create it if not hasattr(env, "needs_all_needs"): env.needs_all_needs = {} if need_id in env.needs_all_needs: if id: raise NeedsDuplicatedId( f"A need with ID {need_id} already exists! " f"This is not allowed. Document {docname}[{lineno}] Title: {title}." ) else: # this is a generated ID raise NeedsDuplicatedId( "Needs could not generate a unique ID for a need with " "the title '{}' because another need had the same title. " "Either supply IDs for the requirements or ensure the " "titles are different. NOTE: If title is being generated " "from the content, then ensure the first sentence of the " "requirements are different.".format(" ".join(title))) # Trim title if it is too long max_length = env.app.config.needs_max_title_length if max_length == -1 or len(title) <= max_length: trimmed_title = title elif max_length <= 3: trimmed_title = title[:max_length] else: trimmed_title = title[:max_length - 3] + "..." # Add the need and all needed information needs_info = { "docname": docname, "lineno": lineno, "target_node": target_node, "external_url": external_url, "content_node": None, # gets set after rst parsing "type": need_type, "type_name": type_name, "type_prefix": type_prefix, "type_color": type_color, "type_style": type_style, "status": status, "tags": tags, "id": need_id, "title": trimmed_title, "full_title": title, "content": content, "collapse": collapse, "style": style, "layout": layout, "template": template, "pre_template": pre_template, "post_template": post_template, "hide": hide, "parts": {}, "is_part": False, "is_need": True, "parent_need": None, "is_external": is_external or False, "external_css": external_css or "external_link", "is_modified": False, # needed by needextend "modifications": 0, # needed by needextend } # needs_extra_options = env.config.needs_extra_options.keys() needs_extra_option_names = NEEDS_CONFIG.get("extra_options").keys() _merge_extra_options(needs_info, kwargs, needs_extra_option_names) needs_global_options = env.config.needs_global_options _merge_global_options(app, needs_info, needs_global_options) link_names = [x["option"] for x in env.config.needs_extra_links] for keyword in kwargs: if keyword not in needs_extra_option_names and keyword not in link_names: raise NeedsInvalidOption( "Unknown Option {}. " "Use needs_extra_options or needs_extra_links in conf.py" "to define this option.".format(keyword)) # Merge links copy_links = [] for link_type in env.config.needs_extra_links: # Check, if specific link-type got some arguments during method call if link_type["option"] not in kwargs and link_type[ "option"] not in needs_global_options: # if not we set no links, but entry in needS_info must be there links = [] elif link_type["option"] in needs_global_options and ( link_type["option"] not in kwargs or len(str(kwargs[link_type["option"]])) == 0): # If it is in global option, value got already set during prior handling of them links_string = needs_info[link_type["option"]] links = _read_in_links(links_string) else: # if it is set in kwargs, take this value and maybe override set value from global_options links_string = kwargs[link_type["option"]] links = _read_in_links(links_string) needs_info[link_type["option"]] = links needs_info["{}_back".format(link_type["option"])] = [] if "copy" not in link_type: link_type["copy"] = False if link_type["copy"] and link_type["option"] != "links": copy_links += links # Save extra links for main-links needs_info["links"] += copy_links # Set copied links to main-links env.needs_all_needs[need_id] = needs_info # Template builds ############################## # template if needs_info["template"]: new_content = _prepare_template(app, needs_info, "template") # Overwrite current content content = new_content needs_info["content"] = new_content # pre_template if needs_info["pre_template"]: pre_content = _prepare_template(app, needs_info, "pre_template") needs_info["pre_content"] = pre_content else: pre_content = None # post_template if needs_info["post_template"]: post_content = _prepare_template(app, needs_info, "post_template") needs_info["post_content"] = post_content else: post_content = None if needs_info["is_external"]: return [] if needs_info["hide"]: return [target_node] # Adding of basic Need node. ############################ # Title and meta data information gets added alter during event handling via process_need_nodes() # We just add a basic need node and render the rst-based content, because this can not be done later. # style_classes = ['need', type_name, 'need-{}'.format(type_name.lower())] # Used < 0.4.4 style_classes = ["need", f"need-{need_type.lower()}"] if style: style_classes.append(style) node_need = Need("", classes=style_classes, ids=[need_id], refid=need_id) # Render rst-based content and add it to the need-node node_need_content = _render_template(content, docname, lineno, state) need_parts = find_parts(node_need_content) update_need_with_parts(env, needs_info, need_parts) node_need += node_need_content.children needs_info["content_node"] = node_need return_nodes = [target_node] + [node_need] if pre_content: node_need_pre_content = _render_template(pre_content, docname, lineno, state) pre_container = nodes.container() pre_container += node_need_pre_content.children return_nodes = node_need_pre_content.children + return_nodes if post_content: node_need_post_content = _render_template(post_content, docname, lineno, state) post_container = nodes.container() post_container += node_need_post_content.children return_nodes = return_nodes + node_need_post_content.children return return_nodes