diff --git a/docs/changelog.rst b/docs/changelog.rst index 46d4e4b95..9ce6991e1 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -9,6 +9,8 @@ Changelog (`#168 `_) * Bugfix: Needs nodes get ``ids`` set directly, to avoid empty ids given by sphinx or other extensions for need-nodes. (`#193 `_) +* Bugfix: :ref:`needimport` supports extra options and extra fields. + (`#227 `_) * Bugfix: Using correct indention for pre and post_template function of needs. diff --git a/docs/directives/needimport.rst b/docs/directives/needimport.rst index 1c6aa3904..4794079b1 100644 --- a/docs/directives/needimport.rst +++ b/docs/directives/needimport.rst @@ -16,6 +16,9 @@ The directive **.. needimport::** can be used in all rst-documents. Simply write :tags: imported;external :hide: :filter: "test" in tags + :template: template.rst + :pre_template: pre_template.rst + :post_template: post_template.rst The directive needs an absolute or relative path as argument. If the path is relative, an absolute path gets calculated with the folder of the **conf.py** as basedir. @@ -43,4 +46,13 @@ in :ref:`needfilter`. The sphinx project owner is responsible for a correct configuration for internal and external needs. There is no automatic typ transformation during an import. +Customization +------------- +The following options can be set, which overwrite the related options in the imported need itself. +So you can decide during import what kind of layout or style is used. +* layout +* style +* template +* pre_template +* post_template diff --git a/setup.py b/setup.py index 0fc5eec60..698993089 100644 --- a/setup.py +++ b/setup.py @@ -11,6 +11,7 @@ 'matplotlib<=3.0.3.;python_version<"3.6"', # last version with py3.5 support 'matplotlib;python_version>="3.6"', 'MarkupSafe<1.3.0.;python_version<"3.6"', + 'Jinja2<=2.11.3.;python_version<"3.6"', 'm2r'] setup( diff --git a/sphinxcontrib/needs/api/need.py b/sphinxcontrib/needs/api/need.py index 76531e098..a062bc613 100644 --- a/sphinxcontrib/needs/api/need.py +++ b/sphinxcontrib/needs/api/need.py @@ -6,6 +6,7 @@ from docutils.statemachine import ViewList from jinja2 import Template from sphinx.util.nodes import nested_parse_with_titles +from typing import List, Union import sphinxcontrib.needs.directives.need from sphinxcontrib.needs.api.exceptions import ( @@ -144,7 +145,10 @@ def run(): if tags is None: tags = [] if len(tags) > 0: - tags = [tag.strip() for tag in re.split(";|,", tags)] + + # 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(): @@ -366,11 +370,17 @@ def _render_template(content, docname, lineno, state): return node_need_content -def _read_in_links(links_string): +def _read_in_links(links_string: Union[str, List[str]]): # Get links links = [] if links_string: - for link in re.split(";|,", links_string): + # Check if links_string is really a string, otherwise it will be a list, which can be used + # without modifications + if isinstance(links_string, str): + link_list = re.split(";|,", links_string) + else: + link_list = links_string + for link in link_list: if link.isspace(): logger.warning('Grubby link definition found in need {}. ' 'Defined link contains spaces only.'.format(id)) diff --git a/sphinxcontrib/needs/directives/needimport.py b/sphinxcontrib/needs/directives/needimport.py index ad8b14590..6cc912481 100644 --- a/sphinxcontrib/needs/directives/needimport.py +++ b/sphinxcontrib/needs/directives/needimport.py @@ -8,6 +8,7 @@ from docutils.parsers.rst import Directive from jinja2 import Template +from sphinxcontrib.needs.api import add_need from sphinxcontrib.needs.filter_common import filter_single_need from sphinxcontrib.needs.utils import logger @@ -26,7 +27,12 @@ class NeedimportDirective(Directive): 'hide': directives.flag, 'filter': directives.unchanged_required, 'id_prefix': directives.unchanged_required, - 'tags': directives.unchanged_required + 'tags': directives.unchanged_required, + 'style': directives.unchanged_required, + 'layout': directives.unchanged_required, + 'template': directives.unchanged_required, + 'pre_template': directives.unchanged_required, + 'post_template': directives.unchanged_required, } final_argument_whitespace = True @@ -78,19 +84,11 @@ def run(self): if filter_string is None: needs_list_filtered[key] = need else: - filter_context = { - "tags": need["tags"], - "status": need["status"], - "type": need["type"], - "id": need["id"], - "title": need["title"], - "links": need["links"], - # "search": re.search, - # Support both ways of addressing the description, as "description" is used in json file, but - # "content" is the sphinx internal name for this kind of information - "content": need["description"], - "description": need["description"] - } + filter_context = {key: value for key, value in need.items()} + + # Support both ways of addressing the description, as "description" is used in json file, but + # "content" is the sphinx internal name for this kind of information + filter_context['content'] = need['description'] try: if filter_single_need(filter_context, filter_string): needs_list_filtered[key] = need @@ -107,11 +105,12 @@ def run(self): for key, need in needs_list.items(): for id in needs_ids: - # Manipulate links - if id in need["links"]: - for n, link in enumerate(need["links"]): - if id == link: - need["links"][n] = "".join([id_prefix, id]) + # Manipulate links in all link types + for extra_link in env.config.needs_extra_links: + if extra_link['option'] in need.keys() and id in need[extra_link['option']]: + for n, link in enumerate(need[extra_link['option']]): + if id == link: + need[extra_link['option']][n] = "".join([id_prefix, id]) # Manipulate descriptions # ToDo: Use regex for better matches. need["description"] = need["description"].replace(id, "".join([id_prefix, id])) @@ -120,15 +119,46 @@ def run(self): for key, need in needs_list.items(): need["tags"] = need["tags"] + tags - template_location = os.path.join(os.path.dirname(os.path.abspath(__file__)), "needimport_template.rst") - with open(template_location, "r") as template_file: - template_content = template_file.read() - template = Template(template_content) - content = template.render(needs_list=needs_list, hide=hide, id_prefix=id_prefix) - self.state_machine.insert_input(content.split('\n'), - self.state_machine.document.attributes['source']) - - return [] + need_nodes = [] + for key, need in needs_list.items(): + # Set some values based on given option or value from imported need. + need['template'] = self.options.get("template", getattr(need, 'template', None)) + need['pre_template'] = self.options.get("pre_template", getattr(need, 'pre_template', None)) + need['post_template'] = self.options.get("post_template", getattr(need, 'post_template', None)) + need['layout'] = self.options.get("layout", getattr(need, 'layout', None)) + need['style'] = self.options.get("style", getattr(need, 'style', None)) + + # The key needs to be different for add_need() api call. + need['need_type'] = need['type'] + + # Replace id, to get unique ids + need['id'] = id_prefix + need['id'] + + need['content'] = need['description'] + # Remove unknown options, as they may be defined in source system, but not in this sphinx project + extra_link_keys = [x['option'] for x in env.config.needs_extra_links] + extra_option_keys = list(env.config.needs_extra_options.keys()) + default_options = ['title', 'status', 'content', 'id', 'tags', 'hide', 'template', 'pre_template', + 'post_template', 'collapse', 'style', 'layout', 'need_type'] + delete_options = [] + for option in need.keys(): + if option not in default_options + extra_link_keys + extra_option_keys: + delete_options.append(option) + + for option in delete_options: + del (need[option]) + + need['docname'] = self.docname + need['lineno'] = self.lineno + + nodes = add_need(env.app, self.state, **need) + need_nodes.extend(nodes) + + return need_nodes + + @property + def docname(self): + return self.state.document.settings.env.docname class VersionNotFound(BaseException):