11<?php
2+
3+ use Codeception \Util \DocumentationHelpers ;
24use Symfony \Component \Finder \Finder ;
35
6+ require __DIR__ . '/vendor/autoload.php ' ;
7+
48class RoboFile extends \Robo \Tasks
59{
6-
10+ use DocumentationHelpers;
11+
12+ const REPO_BLOB_URL = 'https://github.com/Codeception/Codeception/blob ' ;
13+ const STABLE_BRANCH = '4.0 ' ;
14+
715 function post ()
816 {
917 $ title = $ this ->ask ("Post title " );
@@ -97,4 +105,309 @@ function docsBranch($branch)
97105 $ indexFile ->run ();
98106 // $this->taskDeleteDir('source')->run();
99107 }
108+
109+ /**
110+ * @desc generates modules reference from source files
111+ */
112+ public function buildDocs ()
113+ {
114+ $ this ->say ('generating documentation from source files ' );
115+ $ this ->taskComposerInstall ()->run ();
116+ $ this ->buildDocsModules ();
117+ $ this ->buildDocsUtils ();
118+ $ this ->buildDocsCommands ();
119+ $ this ->buildDocsStub ();
120+ $ this ->buildDocsApi ();
121+ $ this ->buildDocsExtensions ();
122+ }
123+
124+ public function buildDocsModules ()
125+ {
126+ $ this ->taskCleanDir ('docs/modules ' )->run ();
127+ $ this ->say ("Modules " );
128+ $ modules = Finder::create ()->files ()->name ('*.php ' )->in (__DIR__ . '/vendor/codeception/module-*/src/Codeception/Module ' );
129+ foreach ($ modules as $ module ) {
130+ $ moduleName = basename (substr ($ module , 0 , -4 ));
131+ $ className = 'Codeception\Module \\' . $ moduleName ;
132+ $ classPath = 'Codeception/Module/ ' . $ moduleName ;
133+ $ repositoryName = basename (substr ($ module , 0 , -1 * (strlen ($ classPath ) + 9 )));
134+ //echo $classPath, " ", $repositoryName, "\n"; continue;
135+ //TODO: take /src/$classPath.php directly from module
136+ $ source = "https://github.com/Codeception/ $ repositoryName/tree/master/src/ $ classPath.php " ;
137+ $ documentationFile = 'docs/modules/ ' . $ moduleName . '.md ' ;
138+ $ sourceMessage = '<p> </p><div class="alert alert-warning">Module reference is taken from the source code. <a href=" ' . $ source . '">Help us to improve documentation. Edit module reference</a></div> ' ;
139+ $ this ->generateDocumentationForClass ($ className , $ documentationFile , $ sourceMessage );
140+ $ this ->postProcessModuleDocFile ($ documentationFile , $ moduleName , $ source );
141+ }
142+ }
143+
144+ private function postProcessModuleDocFile ($ documentationFile , $ name , $ source )
145+ {
146+ $ contents = file_get_contents ($ documentationFile );
147+ $ contents = str_replace ('## ' , '### ' , $ contents );
148+ $ buttons = [
149+ 'source ' => $ source ,
150+ ];
151+ // building version switcher
152+ foreach (['3.1 ' , '2.5 ' , '1.8 ' ] as $ branch ) {
153+ $ buttons [$ branch ] = self ::REPO_BLOB_URL ."/ $ branch/docs/modules/ $ name.md " ;
154+ }
155+ $ buttonHtml = "\n\n" .'<div class="btn-group" role="group" style="float: right" aria-label="..."> ' ;
156+ foreach ($ buttons as $ link => $ url ) {
157+ if ($ link == 'source ' ) {
158+ $ link = "<strong> $ link</strong> " ;
159+ }
160+ $ buttonHtml .= '<a class="btn btn-default" href=" ' .$ url .'"> ' .$ link .'</a> ' ;
161+ }
162+ $ buttonHtml .= '</div> ' ."\n\n" ;
163+
164+ $ contents = $ this ->postProcessStandardElements ($ name , $ buttonHtml . $ contents );
165+ file_put_contents ($ documentationFile , $ contents );
166+ }
167+
168+ public function buildDocsUtils ()
169+ {
170+ $ this ->say ("Util Classes " );
171+ $ utils = [
172+ 'Autoload ' => null ,
173+ 'Fixtures ' => null ,
174+ 'Locator ' => null ,
175+ 'XmlBuilder ' => null ,
176+ 'JsonType ' => 'module-rest ' ,
177+ 'HttpCode ' => 'lib-innerbrowser ' ,
178+ ];
179+ //JsonType is in module-rest, HttpCode - in lib-innerbrowser
180+
181+ foreach ($ utils as $ utilName => $ repositoryName ) {
182+ $ className = '\Codeception\Util \\' . $ utilName ;
183+ $ documentationFile = 'docs/reference/ ' . $ utilName . '.md ' ;
184+ $ this ->documentApiClass ($ documentationFile , $ className , false , $ repositoryName );
185+ $ this ->postProcessFile ($ utilName , $ documentationFile );
186+ }
187+ }
188+
189+ public function buildDocsCommands ()
190+ {
191+ $ this ->say ("Commands " );
192+
193+ $ path = __DIR__ . '/vendor/codeception/codeception/src/Codeception/Command ' ;
194+ $ commands = Finder::create ()->files ()->name ('*.php ' )->depth (0 )->in ($ path );
195+
196+ $ documentationFile = 'docs/reference/Commands.md ' ;
197+ $ commandGenerator = $ this ->taskGenDoc ($ documentationFile );
198+ foreach ($ commands as $ command ) {
199+ $ commandName = basename (substr ($ command , 0 , -4 ));
200+ $ className = '\Codeception\Command \\' . $ commandName ;
201+ $ commandGenerator ->docClass ($ className );
202+ }
203+ $ commandGenerator
204+ ->prepend ("# Console Commands \n" )
205+ ->processClassSignature (function ($ r , $ text ) {
206+ return "## " .$ r ->getShortName ();
207+ })
208+ ->filterMethods (function (ReflectionMethod $ r ) {
209+ return false ;
210+ })
211+ ->run ();
212+
213+ $ this ->postProcessFile ('Commands ' , $ documentationFile );
214+ }
215+
216+ public function buildDocsStub ()
217+ {
218+ $ this ->say ("Stub Classes " );
219+
220+ $ stubFile = 'docs/reference/Stub.md ' ;
221+ $ this ->taskGenDoc ($ stubFile )
222+ ->docClass ('Codeception\Stub ' )
223+ ->filterMethods (function (\ReflectionMethod $ method ) {
224+ if ($ method ->isConstructor () or $ method ->isDestructor ()) {
225+ return false ;
226+ }
227+ if (!$ method ->isPublic ()) {
228+ return false ;
229+ }
230+ if (strpos ($ method ->name , '_ ' ) === 0 ) {
231+ return false ;
232+ }
233+ return true ;
234+ })
235+ ->processMethodDocBlock (
236+ function (\ReflectionMethod $ m , $ doc ) {
237+ $ doc = str_replace (array ('@since ' ), array (' * available since version ' ), $ doc );
238+ $ doc = str_replace (array (' @ ' , "\n@ " ), array (" * " , "\n * " ), $ doc );
239+ return $ doc ;
240+ }
241+ )
242+ ->processProperty (false )
243+ ->run ();
244+
245+ $ mocksDocumentation = <<<EOF
246+ # Mocks
247+
248+ Declare mocks inside `Codeception\Test\Unit` class.
249+ If you want to use mocks outside it, check the reference for [Codeception/Stub](https://github.com/Codeception/Stub) library.
250+ EOF ;
251+
252+ $ mockFile = 'docs/reference/Mock.md ' ;
253+ $ this ->taskGenDoc ($ mockFile )
254+ ->docClass ('Codeception\Test\Feature\Stub ' )
255+ ->docClass ('Codeception\Stub\Expected ' )
256+ ->processClassDocBlock (false )
257+ ->processClassSignature (false )
258+ ->prepend ($ mocksDocumentation )
259+ ->filterMethods (function (\ReflectionMethod $ method ) {
260+ if ($ method ->isConstructor () or $ method ->isDestructor ()) {
261+ return false ;
262+ }
263+ if (!$ method ->isPublic ()) {
264+ return false ;
265+ }
266+ if (strpos ($ method ->name , '_ ' ) === 0 ) {
267+ return false ;
268+ }
269+ if (strpos ($ method ->name , 'stub ' ) === 0 ) {
270+ return false ;
271+ }
272+ return true ;
273+ })
274+ ->run ();
275+
276+ $ this ->postProcessFile ('Stub ' , $ stubFile );
277+ $ this ->postProcessFile ('Mock ' , $ mockFile );
278+ }
279+
280+ public function buildDocsApi ()
281+ {
282+ $ this ->say ("API Classes " );
283+ $ apiClasses = ['Codeception\Module ' , 'Codeception\InitTemplate ' ];
284+
285+ foreach ($ apiClasses as $ apiClass ) {
286+ $ name = (new ReflectionClass ($ apiClass ))->getShortName ();
287+ $ documentationFile = 'docs/reference/ ' . $ name . '.md ' ;
288+ $ this ->documentApiClass ($ documentationFile , $ apiClass , true );
289+ $ this ->postProcessFile ($ name , $ documentationFile );
290+ }
291+ }
292+
293+ public function buildDocsExtensions ()
294+ {
295+ $ this ->say ('Extensions ' );
296+
297+ $ path = __DIR__ . '/vendor/codeception/codeception/ext ' ;
298+ $ extensions = Finder::create ()->files ()->sortByName ()->name ('*.php ' )->in ($ path );
299+
300+ $ extGenerator = $ this ->taskGenDoc ('_includes/extensions.md ' );
301+ foreach ($ extensions as $ extension ) {
302+ $ extensionName = basename (substr ($ extension , 0 , -4 ));
303+ $ className = '\Codeception\Extension \\' . $ extensionName ;
304+ $ extGenerator ->docClass ($ className );
305+ }
306+ $ extGenerator
307+ ->prepend ("# Official Extensions \n" )
308+ ->processClassSignature (function (ReflectionClass $ r , $ text ) {
309+ $ name = $ r ->getShortName ();
310+ return "## $ name \n\n[See Source]( " . self ::REPO_BLOB_URL ."/ " .self ::STABLE_BRANCH . "/ext/ $ name.php) " ;
311+ })
312+ ->filterMethods (function (ReflectionMethod $ r ) {
313+ return false ;
314+ })
315+ ->filterProperties (function ($ r ) {
316+ return false ;
317+ })
318+ ->run ();
319+ }
320+
321+ protected function documentApiClass ($ file , $ className , $ all = false , $ repositoryName = null )
322+ {
323+ if ($ repositoryName === null ) {
324+ $ repositoryUrl = self ::REPO_BLOB_URL ."/ " .self ::STABLE_BRANCH ;
325+ } else {
326+ $ repositoryUrl = 'https://github.com/Codeception/ ' . $ repositoryName . '/blob/master ' ;
327+ }
328+ $ uri = str_replace ('\\' , '/ ' , $ className );
329+ $ source = $ repositoryUrl . "/src $ uri.php " ;
330+
331+ $ this ->taskGenDoc ($ file )
332+ ->docClass ($ className )
333+ ->filterMethods (function (ReflectionMethod $ r ) use ($ all , $ className ) {
334+ return $ all || $ r ->isPublic ();
335+ })
336+ ->append (
337+ '<p> </p><div class="alert alert-warning">Reference is taken from the source code. '
338+ . '<a href=" ' . $ source . '">Help us to improve documentation. Edit module reference</a></div> '
339+ )
340+ ->processPropertySignature (function ($ r ) {
341+ return "\n#### $ " . $ r ->name . "\n\n" ;
342+ })
343+ ->processPropertyDocBlock (function ($ r , $ text ) {
344+ $ modifiers = implode (' ' , \Reflection::getModifierNames ($ r ->getModifiers ()));
345+ $ text = ' * ' . $ modifiers . '* **$ ' . $ r ->name . "** \n" . $ text ;
346+ $ text = preg_replace ("~@(.*?)\s(.*)~ " , 'type `$2` ' , $ text );
347+ return $ text ;
348+ })
349+ ->processClassDocBlock (
350+ function (ReflectionClass $ r , $ text ) {
351+ return $ text . "\n" ;
352+ }
353+ )
354+ ->processMethodSignature (function ($ r , $ text ) {
355+ return "#### {$ r ->name }() \n\n" . ltrim ($ text , '# ' );
356+ })
357+ ->processMethodDocBlock (
358+ function (ReflectionMethod $ r , $ text ) use ($ file , $ source ) {
359+ //$file = str_replace(__DIR__, '', $r->getFileName());
360+ //$source = self::REPO_BLOB_URL."/".self::STABLE_BRANCH. $file;
361+
362+ $ line = $ r ->getStartLine ();
363+ $ text = preg_replace ("~^\s?@(.*?)\s~m " , ' * `$1` $2 ' , $ text );
364+ $ text .= "\n[See source]( $ source#L $ line) " ;
365+ return "\n" . $ text . "\n" ;
366+ }
367+ )
368+ ->reorderMethods ('ksort ' )
369+ ->run ();
370+ }
371+
372+ /**
373+ * @param $name
374+ * @param $contents
375+ * @return mixed|string|string[]|null
376+ */
377+ private function postProcessStandardElements ($ name , $ contents )
378+ {
379+ $ highlight_languages = implode ('| ' , [
380+ 'php ' ,
381+ 'html ' ,
382+ 'bash ' ,
383+ 'yaml ' ,
384+ 'json ' ,
385+ 'xml ' ,
386+ 'sql ' ,
387+ 'gherkin '
388+ ]);
389+ $ contents = preg_replace (
390+ "~```\s?( $ highlight_languages)\b(.*?)```~ms " ,
391+ "{% highlight $1 %} \n$2 \n{% endhighlight %} " ,
392+ $ contents
393+ );
394+ $ contents = str_replace ('{% highlight %} ' , '{% highlight yaml %} ' , $ contents );
395+ $ contents = preg_replace ("~```\s?(.*?)```~ms " , "{% highlight yaml %} \n$1 \n{% endhighlight %} " , $ contents );
396+ // set default language in order not to leave unparsed code inside '```'
397+
398+ $ title = $ name ;
399+ $ contents = "--- \nlayout: doc \ntitle: " . ($ title != "" ? $ title . " - " : "" )
400+ . "Codeception - Documentation \n--- \n\n" . $ contents ;
401+ return $ contents ;
402+ }
403+
404+ /**
405+ * @param $pageName
406+ * @param $documentationFile
407+ */
408+ private function postProcessFile ($ pageName , $ documentationFile )
409+ {
410+ $ contents = $ this ->postProcessStandardElements ($ pageName , file_get_contents ($ documentationFile ));
411+ file_put_contents ($ documentationFile , $ contents );
412+ }
100413}
0 commit comments