Coverage for python / lsst / sphinxutils / ext / utils.py: 44%

35 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-26 08:41 +0000

1"""Utilities for Sphinx extensions.""" 

2 

3from __future__ import annotations 

4 

5import re 

6from dataclasses import dataclass 

7from typing import Self 

8 

9from docutils import nodes 

10 

11__all__ = [ 

12 "RoleContent", 

13 "make_section", 

14] 

15 

16ROLE_DISPLAY_PATTERN = re.compile(r"(?P<display>.+)<(?P<reference>.+)>") 

17 

18 

19@dataclass 

20class RoleContent: 

21 """Interpreted content of a Sphinx role.""" 

22 

23 last_component: bool 

24 """If `True`, the display should show only the last component of a 

25 namespace. 

26 """ 

27 

28 display: str | None 

29 """Custom display content.""" 

30 

31 ref: str 

32 """The reference content. If the role doesn't have a custom display, the 

33 reference will be the role's content. The ``ref`` never includes a ``~`` 

34 prefix. 

35 """ 

36 

37 @classmethod 

38 def parse(cls, role_rawsource: str) -> Self: 

39 """Split the ``rawsource`` of a role into standard components. 

40 

41 Parameters 

42 ---------- 

43 role_rawsource : `str` 

44 The content of the role: its ``rawsource`` attribute. 

45 

46 Returns 

47 ------- 

48 RoleContent 

49 The parsed role content. 

50 

51 Examples 

52 -------- 

53 >>> RoleContent.parse("Tables <lsst.afw.table.Table>") 

54 RoleContent(last_component=False, display=Tables', 

55 ref='lsst.afw.table.Table') 

56 

57 >>> RoleContent.parse("~lsst.afw.table.Table") 

58 RoleContent(last_component=True, display=None, 

59 ref='lsst.afw.table.Table') 

60 """ 

61 if role_rawsource.startswith("~"): 

62 # Only the last part of a namespace should be shown. 

63 last_component = True 

64 # Strip that marker off 

65 role_rawsource = role_rawsource.lstrip("~") 

66 else: 

67 last_component = False 

68 

69 match = ROLE_DISPLAY_PATTERN.match(role_rawsource) 

70 if match: 

71 display = match.group("display").strip() 

72 ref = match.group("reference").strip() 

73 else: 

74 # No suggested display 

75 display = None 

76 ref = role_rawsource.strip() 

77 

78 return cls(last_component=last_component, display=display, ref=ref) 

79 

80 

81def make_section(section_id: str, contents: list[nodes.Node] | None = None) -> nodes.section: 

82 """Make a docutils section node. 

83 

84 Parameters 

85 ---------- 

86 section_id 

87 Section identifier, which is appended to both the ``ids`` and ``names`` 

88 attributes. 

89 contents 

90 List of docutils nodes that are inserted into the section. 

91 

92 Returns 

93 ------- 

94 docutils.nodes.section 

95 Docutils section node. 

96 """ 

97 section = nodes.section() 

98 section["ids"].append(nodes.make_id(section_id)) 

99 section["names"].append(section_id) 

100 if contents is not None: 

101 section.extend(contents) 

102 return section