10
10
use phpDocumentor \Guides \Nodes \DocumentTree \SectionEntryNode ;
11
11
use phpDocumentor \Guides \Nodes \Menu \MenuEntryNode ;
12
12
use phpDocumentor \Guides \Nodes \Menu \MenuNode ;
13
+ use phpDocumentor \Guides \Nodes \Menu \NavMenuNode ;
13
14
use phpDocumentor \Guides \Nodes \Menu \TocNode ;
14
15
use phpDocumentor \Guides \Nodes \Node ;
15
16
use Psr \Log \LoggerInterface ;
18
19
use function assert ;
19
20
use function explode ;
20
21
use function implode ;
22
+ use function in_array ;
21
23
use function preg_match ;
22
24
use function sprintf ;
23
25
use function str_replace ;
24
26
use function str_starts_with ;
25
27
26
28
/** @implements NodeTransformer<MenuNode> */
27
- class TocNodeWithDocumentEntryTransformer implements NodeTransformer
29
+ class MenuNodeAddEntryTransformer implements NodeTransformer
28
30
{
29
31
public function __construct (
30
32
private readonly LoggerInterface $ logger ,
@@ -38,12 +40,13 @@ public function enterNode(Node $node, CompilerContext $compilerContext): Node
38
40
39
41
public function leaveNode (Node $ node , CompilerContext $ compilerContext ): Node |null
40
42
{
41
- if (!$ node instanceof TocNode) {
43
+ if (!$ node instanceof TocNode && ! $ node instanceof NavMenuNode ) {
42
44
return $ node ;
43
45
}
44
46
45
47
$ files = $ node ->getFiles ();
46
48
$ glob = $ node ->hasOption ('glob ' );
49
+ $ globExclude = explode (', ' , $ node ->getOption ('globExclude ' ) . '' );
47
50
48
51
$ documentEntries = $ compilerContext ->getProjectNode ()->getAllDocumentEntries ();
49
52
$ currentPath = $ compilerContext ->getDocumentNode ()->getFilePath ();
@@ -53,48 +56,25 @@ public function leaveNode(Node $node, CompilerContext $compilerContext): Node|nu
53
56
foreach ($ files as $ file ) {
54
57
foreach ($ documentEntries as $ documentEntry ) {
55
58
if (
56
- !self ::isEqualAbsolutePath ($ documentEntry ->getFile (), $ file , $ currentPath , $ glob )
57
- && !self ::isEqualRelativePath ($ documentEntry ->getFile (), $ file , $ currentPath , $ glob )
59
+ !self ::isEqualAbsolutePath ($ documentEntry ->getFile (), $ file , $ currentPath , $ glob, $ globExclude )
60
+ && !self ::isEqualRelativePath ($ documentEntry ->getFile (), $ file , $ currentPath , $ glob, $ globExclude )
58
61
) {
59
62
continue ;
60
63
}
61
64
65
+ if ($ node instanceof TocNode && $ glob && self ::isCurrent ($ documentEntry , $ currentPath )) {
66
+ // TocNodes do not select the current page in glob mode. In a menu we might want to display it
67
+ continue ;
68
+ }
69
+
62
70
$ documentEntriesInTree [] = $ documentEntry ;
63
71
$ menuEntry = new MenuEntryNode ($ documentEntry ->getFile (), $ documentEntry ->getTitle (), [], false , 1 );
64
72
if (!$ node ->hasOption ('titlesonly ' )) {
65
- foreach ($ documentEntry ->getSections () as $ section ) {
66
- // We do not add the main section as it repeats the document title
67
- foreach ($ section ->getChildren () as $ subSectionEntryNode ) {
68
- assert ($ subSectionEntryNode instanceof SectionEntryNode);
69
- $ currentLevel = $ menuEntry ->getLevel () + 1 ;
70
- $ sectionMenuEntry = new MenuEntryNode ($ documentEntry ->getFile (), $ subSectionEntryNode ->getTitle (), [], false , $ currentLevel , $ subSectionEntryNode ->getId ());
71
- $ menuEntry ->addSection ($ sectionMenuEntry );
72
- $ this ->addSubSections ($ sectionMenuEntry , $ subSectionEntryNode , $ documentEntry , $ currentLevel );
73
- }
74
- }
73
+ $ this ->addSubSectionsToMenuEntries ($ documentEntry , $ menuEntry );
75
74
}
76
75
77
- foreach ($ documentEntriesInTree as $ documentEntryInToc ) {
78
- if ($ documentEntryInToc ->isRoot ()) {
79
- // The root page may not be attached to any other
80
- continue ;
81
- }
82
-
83
- if ($ documentEntryInToc ->getParent () !== null && $ documentEntryInToc ->getParent () !== $ compilerContext ->getDocumentNode ()->getDocumentEntry ()) {
84
- $ this ->logger ->warning (sprintf (
85
- 'Document %s has been added to parents %s and %s ' ,
86
- $ documentEntryInToc ->getFile (),
87
- $ documentEntryInToc ->getParent ()->getFile (),
88
- $ compilerContext ->getDocumentNode ()->getDocumentEntry ()->getFile (),
89
- ));
90
- }
91
-
92
- if ($ documentEntryInToc ->getParent () !== null ) {
93
- continue ;
94
- }
95
-
96
- $ documentEntryInToc ->setParent ($ compilerContext ->getDocumentNode ()->getDocumentEntry ());
97
- $ compilerContext ->getDocumentNode ()->getDocumentEntry ()->addChild ($ documentEntryInToc );
76
+ if ($ node instanceof TocNode) {
77
+ $ this ->attachDocumentEntriesToParents ($ documentEntriesInTree , $ compilerContext , $ currentPath );
98
78
}
99
79
100
80
$ menuEntries [] = $ menuEntry ;
@@ -110,6 +90,11 @@ public function leaveNode(Node $node, CompilerContext $compilerContext): Node|nu
110
90
return $ node ;
111
91
}
112
92
93
+ private function isCurrent (DocumentEntryNode $ documentEntry , string $ currentPath ): bool
94
+ {
95
+ return $ documentEntry ->getFile () === $ currentPath ;
96
+ }
97
+
113
98
private function addSubSections (MenuEntryNode $ sectionMenuEntry , SectionEntryNode $ sectionEntryNode , DocumentEntryNode $ documentEntry , int $ currentLevel ): void
114
99
{
115
100
foreach ($ sectionEntryNode ->getChildren () as $ subSectionEntryNode ) {
@@ -120,7 +105,7 @@ private function addSubSections(MenuEntryNode $sectionMenuEntry, SectionEntryNod
120
105
121
106
public function supports (Node $ node ): bool
122
107
{
123
- return $ node instanceof TocNode;
108
+ return $ node instanceof TocNode || $ node instanceof NavMenuNode ;
124
109
}
125
110
126
111
public function getPriority (): int
@@ -129,7 +114,8 @@ public function getPriority(): int
129
114
return 4500 ;
130
115
}
131
116
132
- private static function isEqualAbsolutePath (string $ actualFile , string $ expectedFile , string $ currentFile , bool $ glob ): bool
117
+ /** @param String[] $globExclude */
118
+ private static function isEqualAbsolutePath (string $ actualFile , string $ expectedFile , string $ currentFile , bool $ glob , array $ globExclude ): bool
133
119
{
134
120
if (!self ::isAbsoluteFile ($ expectedFile )) {
135
121
return false ;
@@ -139,10 +125,11 @@ private static function isEqualAbsolutePath(string $actualFile, string $expected
139
125
return true ;
140
126
}
141
127
142
- return self ::isGlob ($ glob , $ actualFile , $ currentFile , $ expectedFile , '/ ' );
128
+ return self ::isGlob ($ glob , $ actualFile , $ currentFile , $ expectedFile , '/ ' , $ globExclude );
143
129
}
144
130
145
- private static function isEqualRelativePath (string $ actualFile , string $ expectedFile , string $ currentFile , bool $ glob ): bool
131
+ /** @param String[] $globExclude */
132
+ private static function isEqualRelativePath (string $ actualFile , string $ expectedFile , string $ currentFile , bool $ glob , array $ globExclude ): bool
146
133
{
147
134
if (self ::isAbsoluteFile ($ expectedFile )) {
148
135
return false ;
@@ -157,12 +144,13 @@ private static function isEqualRelativePath(string $actualFile, string $expected
157
144
return true ;
158
145
}
159
146
160
- return self ::isGlob ($ glob , $ actualFile , $ currentFile , $ absoluteExpectedFile , '' );
147
+ return self ::isGlob ($ glob , $ actualFile , $ currentFile , $ absoluteExpectedFile , '' , $ globExclude );
161
148
}
162
149
163
- private static function isGlob (bool $ glob , string $ documentEntryFile , string $ currentPath , string $ file , string $ prefix ): bool
150
+ /** @param String[] $globExclude */
151
+ private static function isGlob (bool $ glob , string $ documentEntryFile , string $ currentPath , string $ file , string $ prefix , array $ globExclude ): bool
164
152
{
165
- if ($ glob && $ documentEntryFile !== $ currentPath ) {
153
+ if ($ glob && ! in_array ( $ documentEntryFile, $ globExclude ) ) {
166
154
$ file = str_replace ('* ' , '[^\/]* ' , $ file );
167
155
$ pattern = '`^ ' . $ file . '$` ' ;
168
156
@@ -184,4 +172,56 @@ public static function isAbsoluteFile(string $expectedFile): bool
184
172
{
185
173
return str_starts_with ($ expectedFile , '/ ' );
186
174
}
175
+
176
+ /** @param DocumentEntryNode[] $documentEntriesInTree */
177
+ private function attachDocumentEntriesToParents (
178
+ array $ documentEntriesInTree ,
179
+ CompilerContext $ compilerContext ,
180
+ string $ currentPath ,
181
+ ): void {
182
+ foreach ($ documentEntriesInTree as $ documentEntryInToc ) {
183
+ if ($ documentEntryInToc ->isRoot () || $ currentPath === $ documentEntryInToc ->getFile ()) {
184
+ // The root page may not be attached to any other
185
+ continue ;
186
+ }
187
+
188
+ if ($ documentEntryInToc ->getParent () !== null && $ documentEntryInToc ->getParent () !== $ compilerContext ->getDocumentNode ()->getDocumentEntry ()) {
189
+ $ this ->logger ->warning (sprintf (
190
+ 'Document %s has been added to parents %s and %s. The `toctree` directive changes the '
191
+ . 'position of documents in the document tree. Use the `menu` directive to only display a menu without changing the document tree. ' ,
192
+ $ documentEntryInToc ->getFile (),
193
+ $ documentEntryInToc ->getParent ()->getFile (),
194
+ $ compilerContext ->getDocumentNode ()->getDocumentEntry ()->getFile (),
195
+ ));
196
+ }
197
+
198
+ if ($ documentEntryInToc ->getParent () !== null ) {
199
+ continue ;
200
+ }
201
+
202
+ $ documentEntryInToc ->setParent ($ compilerContext ->getDocumentNode ()->getDocumentEntry ());
203
+ $ compilerContext ->getDocumentNode ()->getDocumentEntry ()->addChild ($ documentEntryInToc );
204
+ }
205
+ }
206
+
207
+ private function addSubSectionsToMenuEntries (DocumentEntryNode $ documentEntry , MenuEntryNode $ menuEntry ): void
208
+ {
209
+ foreach ($ documentEntry ->getSections () as $ section ) {
210
+ // We do not add the main section as it repeats the document title
211
+ foreach ($ section ->getChildren () as $ subSectionEntryNode ) {
212
+ assert ($ subSectionEntryNode instanceof SectionEntryNode);
213
+ $ currentLevel = $ menuEntry ->getLevel () + 1 ;
214
+ $ sectionMenuEntry = new MenuEntryNode (
215
+ $ documentEntry ->getFile (),
216
+ $ subSectionEntryNode ->getTitle (),
217
+ [],
218
+ false ,
219
+ $ currentLevel ,
220
+ $ subSectionEntryNode ->getId (),
221
+ );
222
+ $ menuEntry ->addSection ($ sectionMenuEntry );
223
+ $ this ->addSubSections ($ sectionMenuEntry , $ subSectionEntryNode , $ documentEntry , $ currentLevel );
224
+ }
225
+ }
226
+ }
187
227
}
0 commit comments