3
3
---------------
4
4
Provides the ability to mark specific parts of a need with an own id.
5
5
6
- Most voodoo is done in need.py
7
-
8
6
"""
9
7
10
8
from __future__ import annotations
11
9
12
10
import hashlib
13
11
import re
14
12
from collections .abc import Iterable
15
- from typing import cast
16
13
17
14
from docutils import nodes
18
15
from sphinx .application import Sphinx
19
16
from sphinx .environment import BuildEnvironment
17
+ from sphinx .util .docutils import SphinxRole
20
18
from sphinx .util .nodes import make_refnode
21
19
22
20
from sphinx_needs .data import NeedsInfoType , NeedsPartType
23
21
from sphinx_needs .logging import get_logger , log_warning
24
22
from sphinx_needs .nodes import Need
25
23
26
- log = get_logger (__name__ )
24
+ LOGGER = get_logger (__name__ )
27
25
28
26
29
27
class NeedPart (nodes .Inline , nodes .Element ):
30
- pass
28
+ @property
29
+ def title (self ) -> str :
30
+ """Return the title of the part."""
31
+ return self .attributes ["title" ] # type: ignore[no-any-return]
32
+
33
+ @property
34
+ def part_id (self ) -> str :
35
+ """Return the ID of the part."""
36
+ return self .attributes ["part_id" ] # type: ignore[no-any-return]
37
+
38
+ @property
39
+ def need_id (self ) -> str | None :
40
+ """Return the ID of the need this part belongs to."""
41
+ return self .attributes ["need_id" ] # type: ignore[no-any-return]
42
+
43
+ @need_id .setter
44
+ def need_id (self , value : str ) -> None :
45
+ """Set the ID of the need this part belongs to."""
46
+ self .attributes ["need_id" ] = value
47
+
48
+
49
+ _PART_PATTERN = re .compile (r"\(([\w-]+)\)(.*)" , re .DOTALL )
50
+
51
+
52
+ class NeedPartRole (SphinxRole ):
53
+ """
54
+ Role for need parts, which are sub-needs of a need.
55
+ It is used to mark parts of a need with an own id.
56
+ """
57
+
58
+ def run (self ) -> tuple [list [nodes .Node ], list [nodes .system_message ]]:
59
+ # note self.text is the content of the role, with backslash escapes removed
60
+ # TODO perhaps in a future change we should allow escaping parentheses in the part id?
61
+ # and also strip (unescaped) space before/after the title
62
+ result = _PART_PATTERN .match (self .text )
63
+ if result :
64
+ id_ = result .group (1 )
65
+ title = result .group (2 )
66
+ else :
67
+ id_ = hashlib .sha1 (self .text .encode ("UTF-8" )).hexdigest ().upper ()[:3 ]
68
+ title = self .text
69
+ part = NeedPart (title = title , part_id = id_ , need_id = None )
70
+ self .set_source_info (part )
71
+ return [part ], []
31
72
32
73
33
74
def process_need_part (
@@ -36,10 +77,16 @@ def process_need_part(
36
77
fromdocname : str ,
37
78
found_nodes : list [nodes .Element ],
38
79
) -> None :
39
- pass
40
-
41
-
42
- part_pattern = re .compile (r"\(([\w-]+)\)(.*)" , re .DOTALL )
80
+ # note this is called after needs have been processed and parts collected.
81
+ for node in found_nodes :
82
+ assert isinstance (node , NeedPart ), "Expected NeedPart node"
83
+ if node .need_id is None :
84
+ log_warning (
85
+ LOGGER ,
86
+ "Need part not associated with a need." ,
87
+ "part" ,
88
+ node ,
89
+ )
43
90
44
91
45
92
def create_need_from_part (need : NeedsInfoType , part : NeedsPartType ) -> NeedsInfoType :
@@ -67,58 +114,43 @@ def update_need_with_parts(
67
114
) -> None :
68
115
app = env .app
69
116
for part_node in part_nodes :
70
- content = cast (str , part_node .children [0 ].children [0 ]) # ->inline->Text
71
- result = part_pattern .match (content )
72
- if result :
73
- inline_id = result .group (1 )
74
- part_content = result .group (2 )
75
- else :
76
- part_content = content
77
- inline_id = (
78
- hashlib .sha1 (part_content .encode ("UTF-8" )).hexdigest ().upper ()[:3 ]
79
- )
117
+ part_id = part_node .part_id
80
118
81
119
if "parts" not in need :
82
120
need ["parts" ] = {}
83
121
84
- if inline_id in need ["parts" ]:
122
+ if part_id in need ["parts" ]:
85
123
log_warning (
86
- log ,
124
+ LOGGER ,
87
125
"part_need id {} in need {} is already taken. need_part may get overridden." .format (
88
- inline_id , need ["id" ]
126
+ part_id , need ["id" ]
89
127
),
90
128
"duplicate_part_id" ,
91
129
part_node ,
92
130
)
93
131
94
- need ["parts" ][inline_id ] = {
95
- "id" : inline_id ,
96
- "content" : part_content ,
132
+ need ["parts" ][part_id ] = {
133
+ "id" : part_id ,
134
+ "content" : part_node . title ,
97
135
"links" : [],
98
136
"links_back" : [],
99
137
}
100
138
101
- part_id_ref = "{}.{}" . format ( need ["id" ], inline_id )
102
-
139
+ part_node . need_id = need ["id" ]
140
+ part_id_ref = "{}.{}" . format ( need [ "id" ], part_id )
103
141
part_node ["reftarget" ] = part_id_ref
104
142
105
- part_text_node = nodes .Text (part_content )
106
-
107
- part_node .children = []
108
143
node_need_part_line = nodes .inline (ids = [part_id_ref ], classes = ["need-part" ])
109
- node_need_part_line .append (part_text_node )
144
+ node_need_part_line .append (nodes . Text ( part_node . title ) )
110
145
111
146
if docname := need ["docname" ]:
112
- part_id_show = inline_id
113
- part_link_text = f" { part_id_show } "
114
- part_link_node = nodes .Text (part_link_text )
115
-
116
147
part_ref_node = make_refnode (
117
- app .builder , docname , docname , part_id_ref , part_link_node
148
+ app .builder , docname , docname , part_id_ref , nodes . Text ( f" { part_id } " )
118
149
)
119
150
part_ref_node ["classes" ] += ["needs-id" ]
120
151
node_need_part_line .append (part_ref_node )
121
152
153
+ part_node .children = []
122
154
part_node .append (node_need_part_line )
123
155
124
156
0 commit comments