/* ==========================================================================
File : DocumentGenerator.cpp
Class : CDocumentGenerator
Date : 06/16/04
Purpose : "CDocumentGenerator" is a class for generating
documentation for a c++-class from a cpp-file with
rigidly formatted comments.
Description : The class is an intermediate between the application
main dialog and the generator "CDocumentationGenerator"
- used to generate for single cpp-files.
Usage : Call "Generate" with the desired input and output
folder. All cpp-files in the folder will be processed.
If no output name is given, the input folder + '_docs'
will be used.
If a file called 'index.html' is found in the output
folder, it will be scanned for the tags
"<!-- start-class-list -->" and "<!-- end-class-list -->".
Links to the generated ducumentation will be placed
between those two tags - the original information
removed. If index.html or the two tags are not found,
a new index.html will be created.
If a file html_header.txt is found in the application
directory, it's used as the header of the resulting
HTML-files (up to and including the "<body>"-tag),
otherwise, a default header is created by the
application.
If html_footer.txt is found, it's used as the footer
of the HTML-files (from and including the
"</body>"-tag), otherwise, a default footer is created
by the application.
========================================================================*/
#include "stdafx.h"
#include "DocumentGenerator.h"
#include "TextFile/TextFile.h"
#include "resource.h"
#include "DocumentationGenerator.h"
#include "DiskObject/DiskObject.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
////////////////////////////////////////////////////////////////////
// Public functions
//
CDocumentGenerator::CDocumentGenerator( CListBox* feedback )
/* ============================================================
Function : CDocumentGenerator::CDocumentGenerator
Description : Constructor
Access : Public
Return : void
Parameters : none
Usage : Should normally be created on the stack
============================================================*/
{
m_feedback = feedback;
}
CDocumentGenerator::~CDocumentGenerator( )
/* ============================================================
Function : CDocumentGenerator::~CDocumentGenerator
Description : Destructor
Access : Public
Return : void
Parameters : none
Usage :
============================================================*/
{
}
CString CDocumentGenerator::GetErrorMessage( ) const
/* ============================================================
Function : CDocumentGenerator::GetErrorMessage
Description : Accessor. Getter for "m_errorMessage"
Access : Public
Return : CString - The current error message
Parameters : none
Usage : Call to get an error message, if
appropriate.
============================================================*/
{
return m_errorMessage;
}
void CDocumentGenerator::SetErrorMessage( CString value )
/* ============================================================
Function : CDocumentGenerator::SetErrorMessage
Description : Accessor. Setter for "m_errorMessage"
Access : Public
Return : void
Parameters : CString value - New value
Usage : Call to set the internal error message.
============================================================*/
{
m_errorMessage = value;
if( m_feedback )
m_feedback->AddString( m_errorMessage );
}
#define STATE_STARTING 0
#define STATE_OVERVIEW 1
#define STATE_BETWEEN_FUNCTIONS 2
#define STATE_FUNCTION_HEADER 3
#define STATE_INSIDE_FUNCTION 4
#define INNERSTATE_DESCRIPTION 1
#define INNERSTATE_ACCESS 2
#define INNERSTATE_RETURN 3
#define INNERSTATE_PARAMETERS 4
#define INNERSTATE_USAGE 5
#define MODE_PURPOSE 0
#define MODE_DESCRIPTION 1
#define MODE_USAGE 2
BOOL CDocumentGenerator::Generate( const CString& inputdirectory, const CString& outputdirectory )
/* ============================================================
Function : CDocumentGenerator::Generate
Description : Generates html-files with documentation
from all cpp-files in "inputdirectory".
Access : Public
Return : BOOL - "TRUE" if OK
Parameters : const CString& inputdirectory - dir to
generate from
const CString& outputdirectory - dir to
generate to.
Defaults to empty
Usage : This is the main function of the class -
call to generate information from all
cpp-files in "inputdirectory". If the
function returns "FALSE", call
"GetErrorMessage" for an error message.
If "outputdirectory" is empty, "inputdirectory"
+ '_docs' will be used.
============================================================*/
{
CWaitCursor wait;
CDiskObject dob;
CStringArray stra;
CString source( inputdirectory );
if( source[ source.GetLength() - 1 ] == _TCHAR( '\\' ) )
source = source.Left( source.GetLength() - 1 );
CString dest( outputdirectory );
if( dest.IsEmpty() )
dest = source + _T( "\\docs" );
else
if( dest[ dest.GetLength() - 1 ] == _TCHAR( '\\' ) )
dest = dest.Left( dest.GetLength() - 1 );
dob.EnumFilesInDirectoryWithFilter( _T( "*.cpp" ), source, stra );
int max = stra.GetSize();
for( int t = 0 ; t < max ; t++ )
if( !GenerateDocumentation( source + _T( "\\" ) + stra[ t ] , dest ) )
return FALSE;
return GenerateIndexFile( dest );
}
BOOL CDocumentGenerator::GenerateDocumentation( const CString& inputfilename, const CString& dest )
/* ============================================================
Function : CDocumentGenerator::GenerateDocumentation
Description : Generates documentation for the cpp-file
"inputfilename".
Access : Private
Return : BOOL - "TRUE" if OK.
Parameters : const CString& inputfilename - File to generate
const CString& dest - Folder to generate to
Usage : Main engine of the class. Called for each
cpp-file in the input directory.
============================================================*/
{
BOOL result = FALSE;
CString filename( inputfilename );
CString purpose;
CString description;
CString usage;
CString function_name;
CString function_description;
CString function_returns;
CString function_parameters;
CString function_usage;
CString function_return;
CString function_access;
CString class_name;
CDocumentationGenerator docgen;
CFunction* function = NULL;
int overview_mode = MODE_PURPOSE;
CString mess;
if( !filename.IsEmpty() )
{
CTextFile file( _T( "" ), _T( "\n" ) );
CStringArray stra;
CStringArray output;
if( file.ReadTextFile( filename, stra ) )
{
if( m_feedback )
{
mess.LoadString( IDS_PROCESSING );
m_feedback->AddString( mess + filename );
}
int max = stra.GetSize();
int state = STATE_STARTING;
int innerstate = STATE_STARTING;
int bracketcount = 0;
for( int t = 0 ; t < max ; t++ )
{
CString line = stra[ t ];
BOOL hashflag = FALSE;
if( state == STATE_FUNCTION_HEADER && innerstate == INNERSTATE_USAGE && line.GetLength() > 7 && line.Left( 6 ) == _T( "\t\t\t\t\t\t" ) && line.Left( 7 ) != _T( "\t\t\t\t\t\t\t" ) )
hashflag = TRUE;
line.TrimLeft();
line.TrimRight();
if( hashflag )
line = _T( "#" ) + line;
if( line.GetLength() )
{
line.Replace( _TCHAR( '\t' ), _TCHAR( ' ' ) );
switch( state )
{
case STATE_STARTING:
{
// If starting overview, save and
// switch states
CString cls( _T( "Class :" ) );
if( line.GetLength() >= cls.GetLength() && line.Left( cls.GetLength() ) == cls )
{
line = line.Right( line.GetLength() - cls.GetLength() );
line.TrimLeft();
docgen.SetClass( line );
class_name = line;
}
CString purp( _T( "Purpose :" ) );
if( line.GetLength() >= purp.GetLength() && line.Left( purp.GetLength() ) == purp )
{
line = line.Right( line.GetLength() - purp.GetLength() );
line.TrimLeft();
purpose = line;
state = STATE_OVERVIEW;
overview_mode = MODE_PURPOSE;
}
}
break;
case STATE_OVERVIEW:
{
// If at the end of the comments,
// change state
CString end( _T( "===" ) );
if( line.GetLength() >= end.GetLength() && line.Left( end.GetLength() ) == end )
{
state = STATE_BETWEEN_FUNCTIONS;
}
else
{
CString desc( _T( "Description :" ) );
CString usg( _T( "Usage :" ) );
if( line.GetLength() >= desc.GetLength() && line.Left( desc.GetLength() ) == desc )
{
description = line.Right( line.GetLength() - desc.GetLength() );
overview_mode = MODE_DESCRIPTION;
}
else if( line.GetLength() >= usg.GetLength() && line.Left( usg.GetLength() ) == usg )
{
usage = line.Right( line.GetLength() - usg.GetLength() );
overview_mode = MODE_USAGE;
}
else
{
if( overview_mode == MODE_PURPOSE )
purpose += _T( " " ) + line;
else if( overview_mode == MODE_DESCRIPTION )
description += _T( " " ) + line;
else if( overview_mode == MODE_USAGE )
usage += _T( " " ) + line;
}
}
}
break;
case STATE_BETWEEN_FUNCTIONS:
{
// If we find a function definition,
// save and change state
int found = line.Find( _T( "::" ) );
if( found != -1 )
{
CString functionreturn;
CString classname( line.Left( found ) );
int space = classname.ReverseFind( _TCHAR( ' ' ) );
if( space != -1 )
{
functionreturn = classname.Left( space + 1 );
}
space = classname.Find( _T( "(" ) ); // Looking for macro
if( space == -1 )
{
line = line.Right( line.GetLength() - ( found + 2 ) );
innerstate = STATE_STARTING;
state = STATE_FUNCTION_HEADER;
function_return = functionreturn;
function_name = line;
function_description = _T( "" );
function_returns = _T( "" );
function_parameters = _T( "" );
function_usage = _T( "" );
}
}
}
break;
case STATE_FUNCTION_HEADER:
{
// If end of comments, switch state
if( line.GetLength() > 2 && line.Right ( 2 ) == _T( "*/" ) )
{
state = STATE_INSIDE_FUNCTION;
}
else
{
switch( innerstate )
{
case STATE_STARTING:
{
CString desc( _T( "Description :" ) );
if( line.GetLength() >= desc.GetLength() && line.Left( desc.GetLength() ) == desc )
{
line =line.Right( line.GetLength() - desc.GetLength() );
line.TrimLeft();
function_description = line;
innerstate++;
}
}
break;
case INNERSTATE_DESCRIPTION:
{
CString ret( _T( "Access :" ) );
if( line.GetLength() >= ret.GetLength() && line.Left( ret.GetLength() ) == ret )
{
line = line.Right( line.GetLength() - ret.GetLength() );
line.TrimLeft();
function_access = line;
innerstate++;
}
else
function_description += _T( " " ) + line;
}
break;
case INNERSTATE_ACCESS:
{
CString access( _T( "Return :" ) );
if( line.GetLength() >= access.GetLength() && line.Left( access.GetLength() ) == access )
{
line = line.Right( line.GetLength() - access.GetLength() );
line.TrimLeft();
function_returns = line;
innerstate++;
}
else
function_access += _T( " " ) + line;
}
break;
case INNERSTATE_RETURN:
{
CString parameters( _T( "Parameters :" ) );
if( line.GetLength() >= parameters.GetLength() && line.Left( parameters.GetLength() ) == parameters )
{
line = line.Right( line.GetLength() - parameters.GetLength() );
line.TrimLeft();
function_parameters = line;
innerstate++;
}
else
function_returns += _T( " " ) + line;
}
break;
case INNERSTATE_PARAMETERS:
{
CString usage( _T( "Usage :" ) );
if( line.GetLength() >= usage.GetLength() && line.Left( usage.GetLength() ) == usage )
{
line = line.Right( line.GetLength() - usage.GetLength() );
line.TrimLeft();
function_usage = line;
innerstate++;
}
else
{
if( line.Find( _T( "-" ) ) != -1 )
function_parameters += _T( "\n" );
else
function_parameters += _T( " " );
function_parameters += line;
}
}
break;
case INNERSTATE_USAGE:
{
function_usage += _T( " " ) + line;
}
break;
}
}
}
break;
case STATE_INSIDE_FUNCTION:
{
// If the end of the function, save function
// data and switch state
bracketcount += line.Remove( _TCHAR( '{' ) );
bracketcount -= line.Remove( _TCHAR( '}' ) );
if( bracketcount == 0 )
{
function = new CFunction;
function->SetFunctionName( function_name );
function->SetFunctionReturn( function_return );
function->SetDescription( function_description );
function->SetReturns( function_returns );
function->SetParameters( function_parameters );
function->SetUsage( function_usage );
function->SetAccess( function_access );
docgen.AddFunction( function );
state = STATE_BETWEEN_FUNCTIONS;
}
}
break;
}
}
}
if( class_name.GetLength() )
{
docgen.SetPurpose( purpose );
docgen.SetDescription( description );
docgen.SetUsage( usage );
CString newfilename( filename );
int backslash = newfilename.ReverseFind( _TCHAR( '\\' ) );
if( backslash != -1 )
newfilename = newfilename.Right( newfilename.GetLength() - ( backslash + 1 ) );
int point = newfilename.ReverseFind( _TCHAR( '.' ) );
if( point != -1 )
newfilename = newfilename.Left( point );
AddClassName( class_name );
AddBaseFilename( newfilename );
AddDescription( purpose );
newfilename = _T( "\\" ) + newfilename + _T( ".html" );
newfilename = dest + newfilename;
SetHeader( output );
docgen.Generate( output );
SetFooter( output );
CTextFile outfile( _T("html" ), _T( "\n" ) );
if( m_feedback )
{
mess.LoadString( IDS_WRITING );
m_feedback->AddString( mess + newfilename );
}
CDiskObject cdo;
if( cdo.CreateFile( newfilename ) )
{
if( outfile.WriteTextFile( newfilename, output ) )
result = TRUE;
else
{
CString error;
error.LoadString( IDS_COULD_NOT_WRITE_FILE );
error += newfilename;
SetErrorMessage( error + _T( ", " ) + outfile.GetErrorMessage() );
result = FALSE;
}
}
else
{
CString error;
error.LoadString( IDS_COULD_NOT_WRITE_FILE );
error += newfilename;
SetErrorMessage( error + _T( ", " ) + cdo.GetErrorMessage() );
result = FALSE;
}
}
else
result = TRUE;
}
else
{
CString error;
error.LoadString( IDS_COULD_NOT_READ_FILE );
error += inputfilename;
SetErrorMessage( error );
result = FALSE;
}
}
else
{
CString error;
error.LoadString( IDS_NO_FILENAME );
SetErrorMessage( error );
}
return result;
}
void CDocumentGenerator::SetHeader( CStringArray& output )
/* ============================================================
Function : CDocumentGenerator::SetHeader
Description : Fills "output" with an appropriate
HTML-header.
Access : Private
Return : void
Parameters : CStringArray& output - Array to fill
Usage : Call this function to create a HTML-header.
If the file html_header.txt is found in the
application directory, it is used as a
header. Otherwise, a hardcoded version is
used.
============================================================*/
{
CTextFile file;
CString filename;
CStringArray stra;
filename = GetApplicationDirectory() + _T( "html_header.txt" );
if( !file.ReadTextFile( filename, stra ) )
{
output.Add( _T( "<html>" ) );
output.Add( _T( "<head>" ) );
output.Add( _T( "<style>" ) );
output.Add( _T( "body{font-size: 10pt;color: black;font-family: Verdana, Helvetica, Arial, sans-serif;background-color: #ffffff}" ) );
output.Add( _T( "p{font-size: 10pt;color: black;font-family: Verdana, Helvetica, Arial, sans-serif;}" ) );
output.Add( _T( "h2{font-size: 13pt;color: #ff9900;font-family: Verdana, Helvetica, Arial, sans-serif;font-weight: bold;}" ) );
output.Add( _T( "h3{color: #ff9900;font-weight: bold;font-size: 11pt;font-family: Arial, sans-serif;}" ) );
output.Add( _T( "table{background-color:#f0f0ff;}" ) );
output.Add( _T( "td{font-size: 10pt;color: black;font-family: Verdana, Helvetica, Arial, sans-serif;padding:4px;}" ) );
output.Add( _T( "pre{padding-right: 7pt;padding-left: 7pt;padding-bottom: 7pt;font: 9pt 'Courier New', Courier, mono;width: 100%;padding-top: 7pt;white-space: pre;background-color: #fbedbb}" ) );
output.Add( _T( "code{color: #990000;font-family: 'Courier New', Courier, mono;}" ) );
output.Add( _T( "A:link{text-decoration: none}" ) );
output.Add( _T( "A:visited{text-decoration: none}" ) );
output.Add( _T( "A:active{text-decoration: underline}" ) );
output.Add( _T( "A:hover{text-decoration: underline}" ) );
output.Add( _T( "A.top:link{font-size:8pt;color:red;font-family:Verdana,Helvetica,Arial,sans-serif;}" ) );
output.Add( _T( "A.top:visited{font-size:8pt;color:red;font-family:Verdana,Helvetica,Arial,sans-serif;}" ) );
output.Add( _T( "A.top:active{font-size:8pt;color:red;font-family:Verdana,Helvetica,Arial,sans-serif;}" ) );
output.Add( _T( "A.top:hoover{font-size:8pt;color:red;font-family:Verdana,Helvetica,Arial,sans-serif;}" ) );
output.Add( _T( "</style>" ) );
output.Add( _T( "</head>" ) );
output.Add( _T( "<body>" ) );
}
else
output.Append( stra );
}
void CDocumentGenerator::SetFooter( CStringArray& output )
/* ============================================================
Function : CDocumentGenerator::SetFooter
Description : Creates a HTML-footer in "stra"
Access : Private
Return : void
Parameters : CStringArray& output - Array to add
footer to.
Usage : Call to finish a HTML-file.
If the file html_footer.txt is found in the
application directory, it is used as a
footer. Otherwise, a hardcoded version is
used.
============================================================*/
{
CTextFile file;
CString filename;
CStringArray stra;
filename = GetApplicationDirectory() + _T( "html_footer.txt" );
if( !file.ReadTextFile( filename, stra ) )
{
output.Add( _T( "</body>" ) );
output.Add( _T( "</html>" ) );
}
else
output.Append( stra );
}
void CDocumentGenerator::AddClassName( const CString& cls )
/* ============================================================
Function : CDocumentGenerator::AddClassName
Description : Adds a class to the internal class list.
Access : Private
Return : void
Parameters : const CString& cls - Class to add
Usage : Call as soon as a new class is processed.
The internal class list will be used to
create an index file.
============================================================*/
{
m_classes.Add( cls );
}
void CDocumentGenerator::AddDescription( const CString& desc )
/* ============================================================
Function : CDocumentGenerator::AddDescription
Description : Adds a description of a class to the
internal description list.
Access : Private
Return : void
Parameters : const CString& desc - Description to add
Usage : Call as soon as a new class is processed.
The internal class description list will
be used to create an index file.
============================================================*/
{
CString description( desc );
description.Replace( _T( "<" ), _T( "<" ) );
description.Replace( _T( ">" ), _T( ">" ) );
AddCode( description );
m_descriptions.Add( description );
}
void CDocumentGenerator::AddBaseFilename( const CString& str )
/* ============================================================
Function : CDocumentGenerator::AddBaseFilename
Description : Adds a filename to the internal filename
list.
Access : Private
Return : void
Parameters : const CString& str - Filename to add
Usage : Call after a cpp-file has been processed.
The filenames are used to generate an index
file.
============================================================*/
{
m_baseFilename.Add( str );
}
BOOL CDocumentGenerator::GenerateIndexFile( const CString& dest )
/* ============================================================
Function : CDocumentGenerator::GenerateIndexFile
Description : Generates an index file for all the
document files created.
Access : Private
Return : BOOL - "TRUE" if OK.
Parameters : const CString& dest - Directory to create
index file in
Usage : Call to generate an index file after the
individual cpp-files are created.
============================================================*/
{
BOOL result = TRUE;
CStringArray stra;
int max = m_classes.GetSize();
for( int t = 0 ; t < max ; t++ )
{
if( m_classes[ t ].GetLength() )
{
CString link;
link.Format( _T( "<p><a href='%s.html'>%s</a> - %s</p>" ), m_baseFilename[ t ], m_classes[ t ], m_descriptions[ t ] );
stra.Add( link );
}
}
SortStringArray( stra );
CString filename = dest + _T( "\\index.html" );
// First of all, we check if there is
// already an index.html in dest
CDiskObject cdo;
CTextFile file( _T( "" ), _T( "\n" ) );
if( m_feedback )
{
CString mess;
mess.LoadString( IDS_WRITING );
m_feedback->AddString( mess + filename );
}
BOOL eating = FALSE;
if( cdo.FileExists( filename ) )
{
CStringArray index;
BOOL insertion = FALSE;
if( file.ReadTextFile( filename, index ) )
{
int max = index.GetSize();
for( int t = 0 ; t < max ; t++ )
{
if( index[ t ].Find( _T( "<!-- start-class-list -->" ) ) != -1 )
{
t++;
eating = TRUE;
while( eating && index.GetSize() > t )
{
if( index[ t ].Find( _T( "<!-- end-class-list -->" ) ) != -1 )
eating = FALSE;
else
index.RemoveAt( t );
}
index.InsertAt( t, &stra );
if( !eating )
insertion = TRUE;
t = max;
}
}
}
if( insertion )
{
if( !file.WriteTextFile( filename, index ) )
{
SetErrorMessage( file.GetErrorMessage() );
result = FALSE;
}
}
else
{
// Not the expected format in index.html
CStringArray stuff;
SetHeader( stuff );
stuff.Add( _T( "<h2>Classes</h2>" ) );
stuff.Add( _T( "<!-- start-class-list -->" ) );
stra.InsertAt( 0, &stuff );
stra.Add( _T( "<!-- end-class-list -->" ) );
SetFooter( stra );
if( !file.WriteTextFile( filename, stra ) )
{
SetErrorMessage( file.GetErrorMessage() );
result = FALSE;
}
}
}
else
{
CStringArray stuff;
SetHeader( stuff );
stuff.Add( _T( "<h2>Classes</h2>" ) );
stuff.Add( _T( "<!-- start-class-list -->" ) );
stra.InsertAt( 0, &stuff );
stra.Add( _T( "<!-- end-class-list -->" ) );
SetFooter( stra );
if( !file.WriteTextFile( filename, stra ) )
{
SetErrorMessage( file.GetErrorMessage() );
result = FALSE;
}
}
return result;
}
CString GetApplicationDirectory()
{
// Returns the folder where the application
// is installed
TCHAR buffer[ _MAX_PATH + 1 ];
::GetModuleFileName( NULL, buffer, _MAX_PATH );
CString path( buffer );
int find = path.ReverseFind( _TCHAR( '\\' ) );
if( find != -1 )
path = path.Left( find );
path += _TCHAR( '\\' );
return path;
}
void SortStringArray( CStringArray& stra )
{
BOOL replacing = TRUE;
while( replacing )
{
replacing = FALSE;
int max = stra.GetSize();
for( int t = 0 ; t < max - 1 ; t++ )
{
if( stra[ t ] > stra[ t + 1 ] )
{
replacing = TRUE;
CString temp( stra[ t ] );
stra[ t ] = stra[ t + 1 ];
stra[ t + 1 ] = temp;
}
}
}
}