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:
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:
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