engineDoc.cpp

Engine/source/console/engineDoc.cpp

Documentation generator for the current TorqueScript-based engine API.

More...

Public Defines

Public Variables

Used to track unique groups encountered during the dump process.

Public Functions

DefineEngineFunction(dumpEngineDocs , bool , (const char *outputFile) , "Dumps the engine scripting documentation <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the specified <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> overwriting any existing <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">content.\n</a>" "@param outputFile The relative or absolute output <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> path and <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">name.\n</a>" "@return Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">successful.\n</a>" "@ingroup Console" )
dumpClassHeader(Stream & stream, const char * usage, const char * className, const char * superClassName)
dumpDoc(Stream & stream, const char * text, bool checkUngrouped)
bool
dumpEngineDocs(const char * outputFile)
dumpEnums(Stream & stream)
dumpFunction(Stream & stream, bool isClassMethod, Namespace::Entry * entry)
dumpGroupStart(Stream & stream, const char * aName, const char * aDocs)
dumpNamespaceEntries(Stream & stream, Namespace * g, bool callbacks)
dumpVariable(Stream & stream, Dictionary::Entry * entry, const char * inClass)
dumpVariables(Stream & stream, const char * inClass)

Detailed Description

Documentation generator for the current TorqueScript-based engine API.

Be aware that this generator is solely for the legacy console system and is and will not be useful to the new engine API system. It will go away when the console interop is removed.

Public Defines

USE_UNDOCUMENTED_GROUP() 

Public Variables

HashTable< String, U32 > smDocGroups 

Used to track unique groups encountered during the dump process.

Public Functions

DefineEngineFunction(dumpEngineDocs , bool , (const char *outputFile) , "Dumps the engine scripting documentation <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> the specified <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> overwriting any existing <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">content.\n</a>" "@param outputFile The relative or absolute output <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a702945180aa732857b380a007a7e2a21">file</a> path and <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">name.\n</a>" "@return Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">successful.\n</a>" "@ingroup Console" )

dumpClasses(Stream & stream)

dumpClassFooter(Stream & stream)

dumpClassHeader(Stream & stream, const char * usage, const char * className, const char * superClassName)

dumpClassMember(Stream & stream, const AbstractClassRep::Field & field)

dumpDoc(Stream & stream, const char * text, bool checkUngrouped)

dumpEngineDocs(const char * outputFile)

dumpEnum(Stream & stream, const EngineTypeInfo * type)

dumpEnums(Stream & stream)

dumpFragment(Stream & stream, ConsoleDocFragment * fragment)

dumpFunction(Stream & stream, bool isClassMethod, Namespace::Entry * entry)

dumpGroupEnd(Stream & stream)

dumpGroupStart(Stream & stream, const char * aName, const char * aDocs)

dumpNamespaceEntries(Stream & stream, Namespace * g, bool callbacks)

dumpVariable(Stream & stream, Dictionary::Entry * entry, const char * inClass)

dumpVariables(Stream & stream, const char * inClass)

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