Documentation

Here we want to show how to have the same element multiple times in the Documentation.

Howto:

You do have to patch your conf.py to get this working:

Changes in conf.py
202
203# define configuration
204# patching of links is been need for useblocks sphinx-test-reports
205needs_id_prefixes = [
206    {
207    "postfix": "",
208    "prefix":  "A_",
209    "prefix_after_type": True,
210    "paths": ["components/A/"],
211    "links": ["satisfies", "links",],
212    },
213    {
214    "postfix": "_B",
215    "prefix":  "",
216    "prefix_after_type": False,
217    "paths": ["components/B/"],
218    "links": ["satisfies", "links",],
219    },
220    {
221    "postfix": "",
222    "prefix":  "C_",
223    "prefix_after_type": False,
224    "paths": ["components/C/"],
225    "links": ["satisfies", "links",],
226    },
227]
228
229# validate that paths not overlap each other
230for i in range(len(needs_id_prefixes)):
231    for j in range(len(needs_id_prefixes)):
232        if i != j:
233            i_paths = needs_id_prefixes[i]["paths"]
234            j_paths = needs_id_prefixes[i]["paths"]
235            for k in range(len(i_paths)):
236                for l in range(len(j_paths)):
237                    if k != l:
238                        overlap: bool = False
239                        overlap = overlap or i_paths[k].startswith(j_paths[l])
240                        overlap = overlap or j_paths[l].startswith(i_paths[k])
241                        if overlap:
242                            print("Warning: in needs_id_prefixes")
243                            print("It is not allowed to have overlapping 'paths': " + i_paths[k] + " " + j_paths[l])
244
245# patch NeedCheckContext
246
247from sphinx_needs.filter_common import NeedCheckContext
248
249
250NeedCheckContext.needs_id_prefixes = needs_id_prefixes
251
252def this_prefix(self) -> bool:
253    if self._origin_docname is None:
254        raise ValueError("`this_doc` can not be used in this context")
255
256    result: bool = False
257    check_any_prefix_match_current_file: bool = False
258    check_any_prefix_match_current_need: bool = False
259    for needs_id_prefix in needs_id_prefixes:
260        for path in needs_id_prefix['paths']:
261            result = result or \
262                (self._origin_docname.startswith(path) and self._need["docname"].startswith(path))
263            check_any_prefix_match_current_file = check_any_prefix_match_current_file or \
264                self._origin_docname.startswith(path)
265            check_any_prefix_match_current_need = check_any_prefix_match_current_need or \
266                self._need["docname"].startswith(path)
267
268    if check_any_prefix_match_current_file:
269        return result
270    else:
271        # The current file does not fit in any prefix area
272        # -> Return True if need is even not part of any prefix area
273        return not check_any_prefix_match_current_need
274
275NeedCheckContext.this_prefix = this_prefix
276
277#function to patch ids
278def patch_id(id:str, config: dict):
279    new_id: str = ""
280
281    if "prefix" in config:
282        patched: bool = False
283        if "prefix_after_type" in config and config["prefix_after_type"]:
284            for type_prefix in config['type_prefixes']:
285                if id.startswith(type_prefix):
286                    id_without_type_prefix:str = id[len(type_prefix):]
287                    new_id = type_prefix + config["prefix"] + id_without_type_prefix
288                    patched = True
289                    break
290
291        if not patched:
292            new_id = config["prefix"] + id
293    else:
294        new_id = id
295
296    if "postfix" in config:
297        new_id = new_id + config["postfix"]
298    return new_id
299
300import re
301
302#function to patch links
303def patch_links(link:str, config: dict) -> str:
304    new_link: str = ''
305    if len(link) > 0:
306        #link_split = link.split(sep=',')
307        link_split = re.split(r"[,;]", link)
308        links_main_patched: list = []
309        for i in range(len(link_split)):
310            link_split[i] = link_split[i].strip()
311            link_main_part = link_split[i].split(sep='.', maxsplit=1)
312            link_main = link_main_part[0]
313            link_main_patched = patch_id(link_main, config)
314            if len(link_main_part) == 1:
315                links_main_patched.append(link_main_patched)
316            else:
317                link_main_patched_merged = link_main_patched + '.'+ link_main_part[1]
318                links_main_patched.append(link_main_patched_merged)
319
320        result = ', '.join(links_main_patched)
321        return result
322
323    else:
324        return link
325
326# function to change needs, before we generate a need
327from sphinx_needs.config import NeedsSphinxConfig
328import aspectlib
329
330@aspectlib.Aspect
331def changeid(*args, **kwargs):
332    print('changeid')
333    print('before hook:')
334    print("Positional arguments:", args)
335    print("Keyword arguments:", kwargs)
336    id = kwargs['id']
337    print('id: ' + str(id))
338    need_type = kwargs['need_type']
339    print('need_type: ' + str(need_type))
340    app = args[0]
341    print('app: ' + str(app))
342    state = args[1]
343    print('state: ' + str(state))
344    docname = ''
345    if len(args) >= 3:
346        docname = args[2]
347    elif 'docname' in kwargs:
348        docname = kwargs['docname']
349    else:
350        docname = state.document.settings.env.docname
351    print('docname: ' + str(docname))
352
353    needs_config = NeedsSphinxConfig(app.config)
354    needs_types = needs_config.types
355    type_prefixes = [t['prefix'] for t in needs_types]
356
357    found: bool = False
358    for config in needs_id_prefixes:
359        if 'type_prefixes' not in config:
360            # add type_prefixes to config
361            config['type_prefixes'] = type_prefixes
362
363        for path in config["paths"]:
364            if docname.startswith(path):
365                found = True
366                new_id = patch_id(id, config)
367                print('patched id: ' + str(new_id))
368                kwargs['id'] = new_id
369                for link in config["links"]:
370                    if link in kwargs and len(kwargs[link]) > 0:
371                        linkcontent = kwargs[link]
372                        patched_linkcontent = patch_links(linkcontent, config)
373                        print('patched link: ' + str(link) + ' from: ' + str(linkcontent) +' to:' + str(patched_linkcontent))
374                        kwargs[link] = patched_linkcontent
375            if found:
376                break
377        if found:
378            break
379
380    print('call original function:')
381    result = yield aspectlib.Proceed(*args, **kwargs)
382    print('after hook:')
383    yield aspectlib.Return(result)
384
385#import function to be extended
386import sphinx_needs.api
387
388sphinx_needs.api.add_need = changeid(sphinx_needs.api.add_need)
389
390from docutils import nodes
391from sphinx_needs.data import SphinxNeedsData
392
393@aspectlib.Aspect
394def changeid_for_process_need_ref(*args, **kwargs):
395    print('changeid_for_process_need_ref')
396    print('before hook:')
397    print("Positional arguments:", args)
398    print("Keyword arguments:", kwargs)
399
400    app = None
401    env = None
402    docname:str = ''
403    found_nodes = []
404    needs_config = None
405    type_prefixes = None
406    all_needs = None
407
408    if len(args) >= 4:
409        print('process ids:')
410
411        app = args[0]
412        print('app: ' + str(app))
413        env = app.env
414
415        docname = args[2]
416        found_nodes = args[3]
417
418        print('docname: ' + str(docname))
419
420        needs_config = NeedsSphinxConfig(app.config)
421        needs_types = needs_config.types
422        type_prefixes = [t['prefix'] for t in needs_types]
423
424        all_needs = SphinxNeedsData(env).get_needs_view()
425
426    if len(found_nodes) > 0 and len(docname) > 0:
427
428        found: bool = False
429        for config in needs_id_prefixes:
430            if 'type_prefixes' not in config:
431                # add type_prefixes to config
432                config['type_prefixes'] = type_prefixes
433
434            for path in config["paths"]:
435                if docname.startswith(path):
436                    found = True
437                    for node_need_ref in found_nodes:
438                        print("node_need_ref: " + str(node_need_ref))
439                        need_id_full = node_need_ref["reftarget"]
440
441                        need_id_full_patched = patch_links(need_id_full, config)
442                        if need_id_full_patched in all_needs:
443                            node_need_ref["reftarget"] = need_id_full_patched
444                            print('patched needref from: ' + str(need_id_full) +' to:' + str(need_id_full_patched))
445
446                            ref_name: None | str | nodes.Text = node_need_ref.children[0].children[0]
447                            if str(need_id_full) == str(ref_name):
448                                node_need_ref.children[0].children[0] = nodes.Text(need_id_full_patched)  # type: ignore[index]
449                if found:
450                    break
451            if found:
452                break
453
454    print('call original function:')
455    result = yield aspectlib.Proceed(*args, **kwargs)
456    print('after hook:')
457    yield aspectlib.Return(result)
458
459#import function process_need_ref to extend
460import sphinx_needs.roles.need_ref
461
462sphinx_needs.roles.need_ref.process_need_ref = changeid_for_process_need_ref(sphinx_needs.roles.need_ref.process_need_ref)
463
464import sphinx_needs.needs
465from sphinx_needs.roles.need_ref import NeedRef
466sphinx_needs.needs.NODE_TYPES[NeedRef] = changeid_for_process_need_ref(sphinx_needs.roles.need_ref.process_need_ref)

Filter:

You can easily filter for the current file with https://sphinx-needs.readthedocs.io/en/latest/filter.html#filtering-for-needs-on-the-current-page

It is even possible to use the new introduced filter:

.. needtable:: Table of elements within this prefix area
   :show_filters:
   :filter: c.this_prefix()

The this_prefix does support elements which are not part of any prefix.

Fixing Document Headline:

Example:

How-to fix headlines in a toctree
 1################
 2Components Level
 3################
 4
 5.. toctree::
 6   :caption: Contents
 7
 8   original/index
 9   Component A <A/index>
10   Component B <B/index>
11   Component C <C/index>

Todo: test if it working with child headlines, too.

Working Features:

  • needs

  • needpart

  • embedded needs

  • links between needs, needparts and embedded needs

  • filtering with needtable

  • filtering with needlist

  • filtering with needflow

  • use needimport

  • use needextend

  • use sphinx-test-reports

  • need_ref to needs, embedded needs and needpart

Restrictions:

  • This methodology is not working with needpie, see https://github.com/useblocks/sphinx-needs/issues/1449.

  • This methodology is not working with needbar, see https://github.com/useblocks/sphinx-needs/issues/1449.

  • This methodology is not tested with needarch.

  • This methodology is not tested with needuml.

  • This methodology is not tested with needextract.

  • This methodology is not tested with needservice.

  • This methodology is not tested with list2need.

  • This methodology is not tested with needgantt.

  • This methodology is not tested with needsequence.

  • We currently do not have an easy way to filter for elements in a dedicated file location