2
0

engineDoc.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "platform/platform.h"
  23. #include "console/console.h"
  24. #include "console/engineAPI.h"
  25. #include "core/stream/fileStream.h"
  26. #include "console/consoleInternal.h"
  27. #define USE_UNDOCUMENTED_GROUP
  28. /// @file
  29. /// Documentation generator for the current TorqueScript-based engine API.
  30. ///
  31. /// Be aware that this generator is solely for the legacy console system and
  32. /// is and will not be useful to the new engine API system. It will go away
  33. /// when the console interop is removed.
  34. /// Used to track unique groups encountered during
  35. /// the dump process.
  36. static HashTable<String,U32> smDocGroups;
  37. static void dumpDoc( Stream& stream, const char* text, bool checkUngrouped = true )
  38. {
  39. // Extract brief.
  40. String brief;
  41. if( text )
  42. {
  43. const char* briefTag = dStrstr( text, "@brief" );
  44. if( !briefTag )
  45. {
  46. const char* newline = dStrchr( text, '\n' );
  47. if( newline )
  48. {
  49. brief = String( text, newline - text );
  50. text = newline + 1;
  51. }
  52. else
  53. {
  54. brief = text;
  55. text = NULL;
  56. }
  57. }
  58. }
  59. // Write doc comment.
  60. if( !brief.isEmpty() )
  61. {
  62. stream.writeText( "@brief " );
  63. stream.writeText( brief );
  64. stream.writeText( "\r\n\r\n" );
  65. }
  66. if( text )
  67. stream.writeText( text );
  68. #ifdef USE_UNDOCUMENTED_GROUP
  69. if( checkUngrouped && ( !text || !dStrstr( text, "@ingroup" ) ) )
  70. {
  71. smDocGroups.insertUnique( "UNDOCUMENTED", 0 );
  72. stream.writeText( "\r\n@ingroup UNDOCUMENTED\r\n" );
  73. }
  74. #endif
  75. }
  76. static void dumpFragment( Stream& stream, ConsoleDocFragment* fragment )
  77. {
  78. if( !fragment->mText || !fragment->mText[ 0 ] )
  79. return;
  80. // Emit doc text in comment.
  81. stream.writeText( "/*!\r\n" );
  82. stream.writeText( fragment->mText );
  83. stream.writeText( "*/\r\n\r\n" );
  84. // Emit definition, if any.
  85. if( fragment->mDefinition )
  86. {
  87. stream.writeText( fragment->mDefinition );
  88. stream.writeText( "\r\n" );
  89. }
  90. }
  91. static void dumpVariable( Stream& stream,
  92. Dictionary::Entry* entry,
  93. const char* inClass = NULL )
  94. {
  95. // Skip variables defined in script.
  96. if( entry->value.getType() <= ConsoleValueType::cvString )
  97. return;
  98. // Skip internals... don't export them.
  99. if ( entry->mUsage &&
  100. ( dStrstr( entry->mUsage, "@hide" ) || dStrstr( entry->mUsage, "@internal" ) ) )
  101. return;
  102. // Split up qualified name.
  103. Vector< String > nameComponents;
  104. String( entry->name ).split( "::", nameComponents );
  105. if( !nameComponents.size() ) // Safety check.
  106. return;
  107. // Match filter.
  108. if( inClass )
  109. {
  110. // Make sure first qualifier in name components is a
  111. // namespace qualifier matching the given class name.
  112. if( nameComponents.size() <= 1 || dStricmp( nameComponents.first().c_str() + 1, inClass ) != 0 ) // Skip '$'.
  113. return;
  114. }
  115. else
  116. {
  117. // Make sure, this is *not* in a class namespace.
  118. if( nameComponents.size() > 1 && Con::lookupNamespace( nameComponents.first().c_str() + 1 )->mClassRep )
  119. return;
  120. }
  121. // Skip variables for which we can't decipher their type.
  122. ConsoleBaseType* type = ConsoleBaseType::getType( entry->value.type );
  123. if( !type )
  124. {
  125. Con::errorf( "Can't find type for variable '%s'", entry->name );
  126. return;
  127. }
  128. // Write doc comment.
  129. stream.writeText( "/*!\r\n" );
  130. if( !inClass )
  131. {
  132. stream.writeText( "@var " );
  133. stream.writeText( type->getTypeClassName() );
  134. stream.writeText( " " );
  135. stream.writeText( entry->name );
  136. stream.writeText( ";\r\n" );
  137. }
  138. dumpDoc( stream, entry->mUsage );
  139. stream.writeText( "*/\r\n" );
  140. // Write definition.
  141. const U32 numNameComponents = nameComponents.size();
  142. if( !inClass && numNameComponents > 1 )
  143. for( U32 i = 0; i < ( numNameComponents - 1 ); ++ i )
  144. {
  145. stream.writeText( "namespace " );
  146. stream.writeText( nameComponents[ i ] );
  147. stream.writeText( " { " );
  148. }
  149. if( inClass )
  150. stream.writeText( "static " );
  151. if( entry->mIsConstant )
  152. stream.writeText( "const " );
  153. stream.writeText( type->getTypeClassName() );
  154. stream.writeText( " " );
  155. stream.writeText( nameComponents.last() );
  156. stream.writeText( ";" );
  157. if( !inClass && numNameComponents > 1 )
  158. for( U32 i = 0; i < ( numNameComponents - 1 ); ++ i )
  159. stream.writeText( " } " );
  160. stream.writeText( "\r\n" );
  161. }
  162. static void dumpVariables( Stream& stream, const char* inClass = NULL )
  163. {
  164. const U32 hashTableSize = Con::gGlobalVars.hashTable->size;
  165. for( U32 i = 0; i < hashTableSize; ++ i )
  166. for( Dictionary::Entry* entry = Con::gGlobalVars.hashTable->data[ i ]; entry != NULL; entry = entry->nextEntry )
  167. dumpVariable( stream, entry, inClass );
  168. }
  169. static void dumpFunction( Stream &stream,
  170. bool isClassMethod,
  171. Namespace::Entry* entry )
  172. {
  173. String doc = entry->getDocString().trim();
  174. String prototype = entry->getPrototypeString().trim();
  175. // If the doc string contains @hide, skip this function.
  176. if( dStrstr( doc.c_str(), "@hide" ) || dStrstr( doc.c_str(), "@internal" ) )
  177. return;
  178. // Make sure we have a valid function prototype.
  179. if( prototype.isEmpty() )
  180. {
  181. Con::errorf( "Function '%s::%s' has no prototype!", entry->mNamespace->mName, entry->mFunctionName );
  182. return;
  183. }
  184. // See if it's a static method.
  185. bool isStaticMethod = false;
  186. if( entry->mHeader )
  187. isStaticMethod = entry->mHeader->mIsStatic;
  188. // Emit the doc comment.
  189. if( !doc.isEmpty() )
  190. {
  191. stream.writeText( "/*!\r\n" );
  192. // If there's no @brief, take the first line of the doc text body
  193. // as the description.
  194. const char* brief = dStrstr( doc, "@brief" );
  195. if( !brief )
  196. {
  197. String briefStr = entry->getBriefDescription( &doc );
  198. briefStr.trim();
  199. if( !briefStr.isEmpty() )
  200. {
  201. stream.writeText( "@brief " );
  202. stream.writeText(briefStr);
  203. stream.writeText( "\r\n\r\n" );
  204. }
  205. }
  206. stream.writeText( doc );
  207. // Emit @ingroup if it's not a class method.
  208. if ( !isClassMethod && !isStaticMethod ) // Extra static method check for static classes (which will come out as non-class namespaces).
  209. {
  210. const char *group = dStrstr( doc, "@ingroup" );
  211. if( group )
  212. {
  213. char groupName[ 256 ] = { 0 };
  214. dSscanf( group, "@ingroup %s", groupName );
  215. smDocGroups.insertUnique( groupName, 0 );
  216. }
  217. #ifdef USE_UNDOCUMENTED_GROUP
  218. else
  219. {
  220. smDocGroups.insertUnique( "UNDOCUMENTED", 0 );
  221. stream.writeText( "\r\n@ingroup UNDOCUMENTED\r\n" );
  222. }
  223. #endif
  224. }
  225. stream.writeText( "*/\r\n" );
  226. }
  227. #ifdef USE_UNDOCUMENTED_GROUP
  228. else if( !isClassMethod )
  229. {
  230. smDocGroups.insertUnique( "UNDOCUMENTED", 0 );
  231. stream.writeText( "/*! UNDOCUMENTED!\r\n@ingroup UNDOCUMENTED\r\n */\r\n" );
  232. }
  233. #endif
  234. if( isStaticMethod )
  235. stream.writeText( "static " );
  236. stream.writeText( prototype );
  237. stream.writeText( ";\r\n" );
  238. }
  239. static void dumpNamespaceEntries( Stream &stream, Namespace *g, bool callbacks = false )
  240. {
  241. /// Only print virtual on methods that are members of
  242. /// classes as this allows doxygen to properly do overloads.
  243. const bool isClassMethod = g->mClassRep != NULL;
  244. // Go through all the entries in the namespace.
  245. for ( Namespace::Entry *ewalk = g->mEntryList; ewalk; ewalk = ewalk->mNext )
  246. {
  247. S32 eType = ewalk->mType;
  248. // We do not dump script defined functions... only engine exports.
  249. if( eType == Namespace::Entry::ConsoleFunctionType
  250. || eType == Namespace::Entry::GroupMarker )
  251. continue;
  252. if( eType == Namespace::Entry::ScriptCallbackType )
  253. {
  254. if( !callbacks )
  255. continue;
  256. }
  257. else if( callbacks )
  258. continue;
  259. dumpFunction( stream, isClassMethod, ewalk );
  260. }
  261. }
  262. static void dumpClassHeader( Stream &stream,
  263. const char *usage,
  264. const char *className,
  265. const char *superClassName )
  266. {
  267. if ( usage )
  268. {
  269. stream.writeText( "/*!\r\n" );
  270. stream.writeText( usage );
  271. const char *group = dStrstr( usage, "@ingroup" );
  272. if ( group )
  273. {
  274. char groupName[256] = { 0 };
  275. dSscanf( group, "@ingroup %s", groupName );
  276. smDocGroups.insertUnique( groupName, 0 );
  277. }
  278. #ifdef USE_UNDOCUMENTED_GROUP
  279. else
  280. {
  281. smDocGroups.insertUnique( "UNDOCUMENTED", 0 );
  282. stream.writeText( "\r\n@ingroup UNDOCUMENTED\r\n" );
  283. }
  284. #endif
  285. stream.writeText( "\r\n*/\r\n" );
  286. }
  287. else
  288. {
  289. // No documentation string. Check whether ther is a separate
  290. // class doc fragement.
  291. bool haveClassDocFragment = false;
  292. if( className )
  293. {
  294. char buffer[ 1024 ];
  295. dSprintf( buffer, sizeof( buffer ), "@class %s", className );
  296. for( ConsoleDocFragment* fragment = ConsoleDocFragment::smFirst;
  297. fragment != NULL; fragment = fragment->mNext )
  298. if( !fragment->mClass && dStrstr( fragment->mText, buffer ) != NULL )
  299. {
  300. haveClassDocFragment = true;
  301. break;
  302. }
  303. }
  304. #ifdef USE_UNDOCUMENTED_GROUP
  305. if( !haveClassDocFragment )
  306. {
  307. smDocGroups.insertUnique( "UNDOCUMENTED", 0 );
  308. stream.writeText( "/*! UNDOCUMENTED!\r\n@ingroup UNDOCUMENTED\r\n */\r\n" );
  309. }
  310. #endif
  311. }
  312. // Print out appropriate class header
  313. if ( superClassName )
  314. stream.writeText( String::ToString( "class %s : public %s {\r\npublic:\r\n", className, superClassName ? superClassName : "" ) );
  315. else if ( className )
  316. stream.writeText( String::ToString( "class %s {\r\npublic:\r\n", className ) );
  317. else
  318. stream.writeText( "namespace {\r\n" );
  319. }
  320. static void dumpClassMember( Stream &stream,
  321. const AbstractClassRep::Field& field )
  322. {
  323. stream.writeText( "/*!\r\n" );
  324. if( field.pFieldDocs && field.pFieldDocs[ 0 ] )
  325. {
  326. stream.writeText( "@brief " );
  327. String docs( field.pFieldDocs );
  328. S32 newline = docs.find( '\n' );
  329. if( newline == -1 )
  330. stream.writeText( field.pFieldDocs );
  331. else
  332. {
  333. String brief = docs.substr( 0, newline );
  334. String body = docs.substr( newline + 1 );
  335. stream.writeText( brief );
  336. stream.writeText( "\r\n\r\n" );
  337. stream.writeText( body );
  338. }
  339. stream.writeText( "\r\n" );
  340. }
  341. const bool isDeprecated = ( field.type == AbstractClassRep::DeprecatedFieldType );
  342. if( isDeprecated )
  343. stream.writeText( "@deprecated This member is deprecated and its value is always undefined.\r\n" );
  344. stream.writeText( "*/\r\n" );
  345. ConsoleBaseType* cbt = ConsoleBaseType::getType( field.type );
  346. const char* type = ( cbt ? cbt->getTypeClassName() : "" );
  347. if( field.elementCount > 1 )
  348. stream.writeText( String::ToString( "%s %s[ %i ];\r\n", isDeprecated ? "deprecated" : type, field.pFieldname, field.elementCount ) );
  349. else
  350. stream.writeText( String::ToString( "%s %s;\r\n", isDeprecated ? "deprecated" : type, field.pFieldname ) );
  351. }
  352. static void dumpClassFooter( Stream &stream )
  353. {
  354. stream.writeText( "};\r\n\r\n" );
  355. }
  356. static void dumpGroupStart( Stream &stream,
  357. const char *aName,
  358. const char *aDocs = NULL )
  359. {
  360. stream.writeText( String::ToString( "\r\n/*! @name %s\r\n", aName ) );
  361. if ( aDocs )
  362. {
  363. stream.writeText( aDocs );
  364. stream.writeText( "\r\n" );
  365. }
  366. stream.writeText( "@{ */\r\n" );
  367. // Add a blank comment in order to make sure groups are parsed properly.
  368. //Con::printf(" /*! */");
  369. }
  370. static void dumpGroupEnd( Stream &stream )
  371. {
  372. stream.writeText( "/// @}\r\n\r\n" );
  373. }
  374. static void dumpClasses( Stream &stream )
  375. {
  376. Namespace::trashCache();
  377. VectorPtr<Namespace*> vec;
  378. vec.reserve( 1024 );
  379. // We use mHashSequence to mark if we have traversed...
  380. // so mark all as zero to start.
  381. for ( Namespace *walk = Namespace::mNamespaceList; walk; walk = walk->mNext )
  382. walk->mHashSequence = 0;
  383. for(Namespace *walk = Namespace::mNamespaceList; walk; walk = walk->mNext)
  384. {
  385. VectorPtr<Namespace*> stack;
  386. stack.reserve( 1024 );
  387. // Get all the parents of this namespace... (and mark them as we go)
  388. Namespace *parentWalk = walk;
  389. while(parentWalk)
  390. {
  391. if(parentWalk->mHashSequence != 0)
  392. break;
  393. if(parentWalk->mPackage == 0)
  394. {
  395. parentWalk->mHashSequence = 1; // Mark as traversed.
  396. stack.push_back(parentWalk);
  397. }
  398. parentWalk = parentWalk->mParent;
  399. }
  400. // Load stack into our results vector.
  401. while(stack.size())
  402. {
  403. vec.push_back(stack[stack.size() - 1]);
  404. stack.pop_back();
  405. }
  406. }
  407. // Go through previously discovered classes
  408. U32 i;
  409. for(i = 0; i < vec.size(); i++)
  410. {
  411. const char *className = vec[i]->mName;
  412. const char *superClassName = vec[i]->mParent ? vec[i]->mParent->mName : NULL;
  413. // Skip the global namespace, that gets dealt with in dumpFunctions
  414. if(!className)
  415. continue;
  416. // We're just dumping engine functions, then we don't want to dump
  417. // a class that only contains script functions. So, we iterate over
  418. // all the functions.
  419. bool found = false;
  420. for( Namespace::Entry *ewalk = vec[i]->mEntryList; ewalk; ewalk = ewalk->mNext )
  421. {
  422. if( ewalk->mType != Namespace::Entry::ConsoleFunctionType )
  423. {
  424. found = true;
  425. break;
  426. }
  427. }
  428. // If we don't have engine functions and the namespace name
  429. // doesn't match the class name... then its a script class.
  430. if ( !found && !vec[i]->isClass() )
  431. continue;
  432. // If we hit a class with no members and no classRep, do clever filtering.
  433. if(vec[i]->mEntryList == NULL && vec[i]->mClassRep == NULL)
  434. {
  435. // Print out a short stub so we get a proper class hierarchy.
  436. if ( superClassName )
  437. {
  438. // Filter hack; we don't want non-inheriting classes...
  439. dumpClassHeader( stream, NULL, className, superClassName );
  440. dumpClassFooter( stream );
  441. }
  442. continue;
  443. }
  444. // Skip over hidden or internal classes.
  445. if( vec[i]->mUsage &&
  446. ( dStrstr( vec[i]->mUsage, "@hide" ) || dStrstr( vec[i]->mUsage, "@internal" ) ) )
  447. continue;
  448. // Print the header for the class..
  449. dumpClassHeader( stream, vec[i]->mUsage, className, superClassName );
  450. // Dump all fragments for this class.
  451. for( ConsoleDocFragment* fragment = ConsoleDocFragment::smFirst; fragment != NULL; fragment = fragment->mNext )
  452. if( fragment->mClass && dStricmp( fragment->mClass, className ) == 0 )
  453. dumpFragment( stream, fragment );
  454. // Dump member functions.
  455. dumpNamespaceEntries( stream, vec[ i ], false );
  456. // Dump callbacks.
  457. dumpGroupStart( stream, "Callbacks" );
  458. dumpNamespaceEntries( stream, vec[ i ], true );
  459. dumpGroupEnd( stream );
  460. // Dump static member variables.
  461. dumpVariables( stream, className );
  462. // Deal with the classRep (to get members)...
  463. AbstractClassRep *rep = vec[i]->mClassRep;
  464. AbstractClassRep::FieldList emptyList;
  465. AbstractClassRep::FieldList *parentList = &emptyList;
  466. AbstractClassRep::FieldList *fieldList = &emptyList;
  467. if ( rep )
  468. {
  469. // Get information about the parent's fields...
  470. AbstractClassRep *parentRep = vec[i]->mParent ? vec[i]->mParent->mClassRep : NULL;
  471. if(parentRep)
  472. parentList = &(parentRep->mFieldList);
  473. // Get information about our fields
  474. fieldList = &(rep->mFieldList);
  475. // Go through all our fields...
  476. for(U32 j = 0; j < fieldList->size(); j++)
  477. {
  478. const AbstractClassRep::Field &field = (*fieldList)[j];
  479. switch( field.type )
  480. {
  481. case AbstractClassRep::StartArrayFieldType:
  482. case AbstractClassRep::EndArrayFieldType:
  483. break;
  484. case AbstractClassRep::StartGroupFieldType:
  485. dumpGroupStart( stream, field.pGroupname, field.pFieldDocs );
  486. break;
  487. case AbstractClassRep::EndGroupFieldType:
  488. dumpGroupEnd( stream );
  489. break;
  490. default:
  491. case AbstractClassRep::DeprecatedFieldType:
  492. // Skip over fields that are already defined in
  493. // our parent class.
  494. if ( parentRep && parentRep->findField( field.pFieldname ) )
  495. continue;
  496. dumpClassMember( stream, field );
  497. break;
  498. }
  499. }
  500. }
  501. // Close the class/namespace.
  502. dumpClassFooter( stream );
  503. }
  504. }
  505. static void dumpEnum( Stream& stream, const EngineTypeInfo* type )
  506. {
  507. if( !type->getEnumTable() ) // Sanity check.
  508. return;
  509. // Skip internals... don't export them.
  510. if ( type->getDocString() &&
  511. ( dStrstr( type->getDocString(), "@hide" ) || dStrstr( type->getDocString(), "@internal" ) ) )
  512. return;
  513. // Write documentation.
  514. stream.writeText( "/*!\r\n" );
  515. dumpDoc( stream, type->getDocString() );
  516. stream.writeText( "*/\r\n" );
  517. // Write definition.
  518. stream.writeText( "enum " );
  519. stream.writeText( type->getTypeName() );
  520. stream.writeText( " {\r\n" );
  521. const EngineEnumTable& table = *( type->getEnumTable() );
  522. const U32 numValues = table.getNumValues();
  523. for( U32 i = 0; i < numValues; ++ i )
  524. {
  525. const EngineEnumTable::Value& value = table[ i ];
  526. stream.writeText( "/*!\r\n" );
  527. dumpDoc( stream, value.getDocString(), false );
  528. stream.writeText( "*/\r\n" );
  529. stream.writeText( value.getName() );
  530. stream.writeText( ",\r\n" );
  531. }
  532. stream.writeText( "};\r\n" );
  533. }
  534. static void dumpEnums( Stream& stream )
  535. {
  536. for( const EngineTypeInfo* type = EngineTypeInfo::getFirstType();
  537. type != NULL; type = type->getNextType() )
  538. if( type->isEnum() || type->isBitfield() )
  539. dumpEnum( stream, type );
  540. }
  541. static bool dumpEngineDocs( const char *outputFile )
  542. {
  543. // Create the output stream.
  544. FileStream stream;
  545. if ( !stream.open( outputFile, Torque::FS::File::Write ) )
  546. {
  547. Con::errorf( "dumpEngineDocs - Failed to open output file." );
  548. return false;
  549. }
  550. // First dump all global ConsoleDoc fragments.
  551. for( ConsoleDocFragment* fragment = ConsoleDocFragment::smFirst; fragment != NULL; fragment = fragment->mNext )
  552. if( !fragment->mClass )
  553. dumpFragment( stream, fragment );
  554. // Clear the doc groups before continuing,
  555. smDocGroups.clear();
  556. // Dump enumeration types.
  557. dumpEnums( stream );
  558. // Dump all global variables.
  559. dumpVariables( stream );
  560. // Now dump the global functions.
  561. Namespace *g = Namespace::find( NULL );
  562. while( g )
  563. {
  564. dumpNamespaceEntries( stream, g );
  565. // Dump callbacks.
  566. dumpGroupStart( stream, "Callbacks" );
  567. dumpNamespaceEntries( stream, g, true );
  568. dumpGroupEnd( stream );
  569. g = g->mParent;
  570. }
  571. // Now dump all the classes.
  572. dumpClasses( stream );
  573. // Dump pre-declarations for any groups we encountered
  574. // so that we don't have to explicitly define them.
  575. HashTable<String,U32>::Iterator iter = smDocGroups.begin();
  576. for (; iter != smDocGroups.end(); ++iter)
  577. stream.writeText( String::ToString( "/*! @addtogroup %s */\r\n\r\n", iter->key.c_str() ) );
  578. return true;
  579. }
  580. DefineEngineFunction( dumpEngineDocs, bool, ( const char* outputFile ),,
  581. "Dumps the engine scripting documentation to the specified file overwriting any existing content.\n"
  582. "@param outputFile The relative or absolute output file path and name.\n"
  583. "@return Returns true if successful.\n"
  584. "@ingroup Console")
  585. {
  586. return dumpEngineDocs( outputFile );
  587. }