Documenta

Miscellaneous Forums/Blitz Showcase/Documenta

Updated it, no need to change the bmxfolder variable to your installation (and added url to mod title so that the mod can be opened in the IDE):
'
' Documenta.bmx
' BlitzMax Module Documentation Generator
'
' Version: 0.77 (with modifications from Perturbatio)
' Purpose: To create nice help files for BlitzMax.
' Author:  Mikkel Fredborg
'
' Warning: Use this at your own risk! It will overwrite your original BlitzMax
'          Command Reference. Please make sure to back up the documents before
'          running Documenta!
' 
' ToDo: Add better (less hardcoded) css formatting (and make it prettier).
'       Add the ability to import additional comments files.
'       Comment code :)
'
' History: 
'		   0.77 - automatically retrieve bmax folder so that it will run without need to configure
'          0.76 - Added url to module source so that the IDE can open it (PERTURBATIO)
'		   0.75 - Relative file paths
'          0.71 - Compatible with BlitzMax 1.03
'          0.7  - Converts language reference and user guide!
'          0.65 - Minor tweaks
'          0.6  - Navbar works with Firefox
'                 Overwrites original Blitzmax module documentation!!!
'          0.5  - Auto fixes table layout/style
'                 Fix Intro formatting
'          0.4  - Recognizes Global function definitions (ie. Global AppArgs$[]="bbAppArgs")
'          0.3  - Skip Private and Extern sections
'                 Exclude modules which doesn't have any functions
'          0.2  - Examples and Module introductions are included
'          0.1  - Basic stuff
'

'
' Change this to your BlitzMax folder
Global bmaxfolder:String = getenv_("BMXPATH")

'
' Modify these if you want extra stuff
Const SKIP_PRIVATE	= True		' Skips commands private to a module
Const SKIP_EXTERN	= False		' Skip extern commands
Const SKIP_NONDOCED = False		' Skip non documented commands

'
' Fix up path if it isn't slashed
bmaxfolder = StripSlash(bmaxfolder)+"/"
bmaxfolder = bmaxfolder.Replace("","/")
If FileType(StripSlash(bmaxfolder)) = 0
	Notify "Incorrect BlitzMax folder!~n~q"+bmaxfolder+"~q!"
	End
EndIf

'
' Destination folder...only works correctly if it's the bmax folder :)
Global destfolder:String = bmaxfolder

'
' Warn if overwriting
If bmaxfolder = destfolder
	If Proceed("Warning!~n~nThis will overwrite some of the original BlitzMax help files.~nPlease make sure you have a backup of your 'BlitzMax/doc/bmxmods' folder!~n~nDo you wish to continue?",True)<>1
		Print "User aborted...What a sissy :)"
		End
	EndIf
EndIf

'
' Do NOT modify
Const linefeed:String = "~r~n"
Const htmlext:String = ".html"
Const backuppre:String = "documenta_backup_"

Global modlist:TList = New TList
Global funlist:TList = New TList
Global CSS_List:TList = New TList

Type dmod
	
	Field docfile:String

	Field file:String
	Field name:String
	Field info:String
	Field intro:String
	Field funclist:TList = New TList
	
	Method New()
		modlist.addlast Self
	End Method
	
	Method AddFunction:dfunc()
		Local f:dfunc = New dfunc
		f.mymod = Self
		Self.funclist.addlast f
		funlist.addlast f
		Return f
	End Method
	
	Method compare:Int(otherObj:Object)
		Local m:dmod = dmod(otherObj)
		If Not m Then Return 1
		Return Self.name.compare(m.name)
	End Method
End Type

Type dfunc
	Field name:String
	Field sname:String
	Field param:String
	Field desc:String
	Field rets:String
	Field about:String
	Field example:String
	Field exfile:String
	Field mymod:dmod
	
	Field keyword:Int
	
	Method compare:Int(otherObj:Object)
		Local f:dfunc = dfunc(otherObj)
		If Not f Then Return 1
		Return Self.sname.compare(f.sname)
	End Method
End Type

'
' Converted from:
' <a href="http://www.codeguru.com/Cpp/misc/misc/fileanddirectorynaming/article.php/c263/" target="_blank">www.codeguru.com/Cpp/misc/misc/fileanddirectorynaming/article.php/c263/</a>
'
Function GetRelativeFilename:String(currentDirectory:String, absoluteFilename:String)
	
	Local slash			= Asc("/")

	currentDirectory = currentDirectory.Replace("","/")
	absoluteFilename = absoluteFilename.Replace("","/")

	If currentDirectory[currentDirectory.length-1]<>slash
		currentDirectory:+"/"
	EndIf
	
	Local afMarker:Int	= 0
	Local rfMarker:Int	= 0
	Local cdLen:Int		= 0
	Local afLen:Int		= 0
	Local i:Int			= 0
	Local levels:Int	= 0
	Local relativeFilename:String

	cdLen = currentDirectory.length
	afLen = absoluteFilename.length

	' Handle DOS names that are on different drives:
	If (Lower(Chr(currentDirectory[0])) <> Lower(Chr(absoluteFilename[0])))
		' Not on the same drive, so only absolute filename will do
		relativeFilename = absoluteFilename
		Return relativeFilename
	EndIf

	' they are on the same drive, find out how much of the current directory
	' is in the absolute filename
	i = 3
	While((i < afLen) And (i < cdLen) And (Lower(Chr(currentDirectory[i])) = Lower(Chr(absoluteFilename[i]))))
		i:+1
	Wend
	
	If (i = cdLen And (absoluteFilename[i] = slash Or absoluteFilename[i-1] = slash))
		' the whole current directory name is in the file name,
		' so we just trim off the current directory name to get the
		' current file name.
		If(absoluteFilename[i] = slash)
			' a directory name might have a trailing slash but a relative
			' file name should not have a leading one...
			i:+1
		EndIf

		relativeFilename = absoluteFilename[i..afLen]
		Return relativeFilename
	EndIf


	' The file is not in a child directory of the current directory, so we
	' need to step back the appropriate number of parent directories by
	' using ".."s.  First find out how many levels deeper we are than the
	' common directory
	afMarker = i;
	levels = 1;

	' count the number of directory levels we have to go up to get to the
	' common directory
	While(i < cdLen)
		i:+1
		If(currentDirectory[i] = slash)
			' make sure it's not a trailing slash
			i:+1
			If i<cdLen'(currentDirectory[i] != '\0')
				levels:+1
			EndIf
		EndIf
	Wend

	' move the absolute filename marker back to the start of the directory name
	' that it has stopped in.
	While(afMarker > 0 And absoluteFilename[afMarker-1] <> slash)
		afMarker:-1
	Wend

	' add the appropriate number of "../"s.
	rfMarker = 0;
	For i = 0 Until levels
		relativeFilename:+"../"
	Next

	' copy the rest of the filename into the result string
	relativeFilename:+absoluteFilename[afMarker..afLen]

	Return relativeFilename
	
End Function

'
' Scans the BlitzMax module folder to find all modules
Function ScanModFolder(f:String)

	f = StripSlash(f)

	Local files:String[]
	files = LoadDir(f,True)
	
	'
	' Find module files
	For Local fi:String = EachIn files
		If KeyDown(KEY_ESCAPE) Then Exit

		Local file:String = f+"/"+fi
		If FileType(file)=1
			If Lower(fi[fi.length-4..fi.length])=".bmx"
				ScanBMXFile(file)
			EndIf
		Else
			ScanModFolder(file)
		EndIf
		
		FlushMem
	Next
	
End Function

'
' Scans a .bmx file and extracts info from modules
Function ScanBMXFile(file:String)
	
	Local m:dmod
	
	Local bbdoc:String
	Local returns:String
	Local about:String
	
	Local scanmode:Int = 0
	Local keyword:Int = False
	
	Local inextern:Int = False
	Local inprivate:Int = False
	
	Local temp:String
	Local temp2:String
	Local f1:Int
	Local f2:Int
	
	Local stream = ReadFile(file)
	If stream
		While Not Eof(stream)
			l:String = ReadLine(stream)
			
			If SKIP_EXTERN
				If Lower(l[0..6]) = "extern"
					inextern = True
				EndIf
				If Lower(l[0..9]) = "endextern" Or Lower(l[0..10]) = "end extern"
					inextern = False
				EndIf
			EndIf

			If SKIP_PRIVATE
				If Lower(l[0..7]) = "private"
					inprivate = True
				EndIf
				If Lower(l[0..6]) = "public" 
					inprivate = False
				EndIf
			EndIf			
			
			If inextern = False And inprivate = False
			If m = Null
				'
				' module name
				If Lower(l[0..7]) = "module "
					m = New dmod
					m.file = file
					m.name = FormatString(l[7..l.length])
					m.info = "<tr><td class=~qmodinfoleft~q>Module</td><td class=~qmodinforight~q>"+m.name+"</td></tr>"+linefeed
				EndIf
			Else
				'
				' Module info
				If Lower(l[0..10]) = "moduleinfo"
					f1 = l.find("~q",0)+1
					f2 = l.find("~q",f1)
					If f1>0 And f2>0
						temp = l[f1..f2]
						f1 = temp.find(":",0)
						If f1
							temp2 = FormatString(temp[0..f1])
							temp = FormatString(temp[f1+1..temp.length])
						EndIf
						m.info = m.info+"<tr><td class=~qmodinfoleft~q>"+temp2+"</td><td class=~qmodinforight~q>"+temp+"</td></tr>"+linefeed
					EndIf
				EndIf
				
				Local ol = l.length
				
				If Lower(l[0..6])="bbdoc:"
					l = l[6..l.length]
					scanmode = 1
				EndIf

				If Lower(l[0..8])="returns:"
					l = l[8..l.length]
					scanmode = 2
				EndIf
				
				If Lower(l[0..6])="about:"
					l = l[6..l.length]
					scanmode = 3
				EndIf
				
				If scanmode
					If Lower(l[0..6])="endrem" Or Lower(l[0..7])="end rem" Or ol < 2
						scanmode = False
					Else
						If scanmode = 1 bbdoc = bbdoc+FormatString(l,True)+" "
						If scanmode = 2 returns = returns+FormatString(l,True)+" "
						If scanmode = 3 about = about+FormatString(l,True)+" "
					EndIf
				EndIf

				If keyword
					If f:dfunc
						f.desc = bbdoc
						f.rets = returns
						f.about = about
					EndIf
				EndIf

 				If Lower(l[0..7])="global "
 					If bbdoc = ""
 						l = ".."+l
 					EndIf
 				EndIf
					
				If Lower(l[0..9])="function " Or Lower(l[0..9])="keyword: " Or Lower(l[0..7])="global "
					f:dfunc = m.addfunction()
					
					If Lower(l[0..9])="function "
						f.desc	= bbdoc
						f.rets	= returns
						f.about = about
						keyword = False

						f1 = l.find("(")
						f.name = l[9..f1]
						f1 = f1+1
						f2 = l.length
						Local level = 1
						For Local i = f1 Until l.length
							If l[i] = Asc("(") Then level:+1
							If l[i] = Asc(")") Then level:-1
							If level = 0 
								f2 = i
								Exit
							EndIf
						Next
						f.param = l[f1..f2]
					ElseIf Lower(l[0..9])="keyword: "
						keyword = True
						f.name = l[9..l.length]
						f.name = f.name.Replace("~q","")
						f.name = f.name.Trim()
						f.keyword = True
					Else
						keyword = False
						f.name = l[7..l.find("=")]+"()"
						f.name = f.name.Trim()
						f.keyword = True

						f.desc	= bbdoc
						f.rets	= returns
						f.about = about
						keyword = False						
					EndIf
					
					bbdoc = ""
					returns = ""
					about = ""
					
					f.sname = f.name
					For i = 0 Until f.sname.length
						If f.sname[i] = Asc("$") Or f.sname[i] = Asc("#") Or f.sname[i] = Asc("%") Or f.sname[i] = Asc(":") Or f.sname[i] = Asc("!")
							f.sname = f.sname[0..i]
							Exit
						EndIf
					Next
				EndIf
				
			EndIf
			EndIf
		Wend
		CloseFile(stream)
	EndIf
	
	If m <> Null
		Print file
	EndIf
	
	Return m<>Null
	
End Function

'
' Formats a string with links, styles etc.
Function FormatString:String(s:String,aboutmode:Int=False)
	
	Local f1:Int,f2:Int,i:Int
	Local link:String
	
	'
	' Remove non printable characters before and after
	s = s.Trim()
	
	'
	' Remove starting break
	If Lower(s[0..4]) = "<br>"
		s = s[4..s.length]
		s = s.Trim()
	EndIf
	
	'
	' We don't like <p>
	s = s.Replace("<p>","<br><br>")
	s = s.Replace("</p>","<br><br>")
	
	'
	' Fix tables
	s = s.Replace("<table>","<table class=~qsubtable~q>")
	s = s.Replace("<th>","<th class=~qsubth~q>")
	s = s.Replace("<td>","<td class=~qsubtd~q>")
		
	'
	' Auto fix http:// links
	f1 = s.find("http://")
	f2 = s.length
	If f1=>0
		For i = f1 Until s.length
			If s[i]<=32
				f2 = i
				Exit
			EndIf
		Next
		link:String = s[f1..f2]
		s = s[0..f1]+"<a href=~q"+link+"~q target=~q_blank~q>"+link+"</a>"+s[f2..s.length]
	EndIf
	
	If aboutmode
	
		'
		' bbdoc #something = link to another function
		Repeat
			f1 = s.find("#")
			f2 = s.length
			If f1=>0
				For i = f1 Until s.length
					If s[i]<=32 Or s[i]=Asc(",") Or s[i]=Asc(".") Or s[i]=Asc(")") Or s[i]=Asc("(")
						f2 = i
						Exit
					EndIf
				Next
				link:String = s[f1+1..f2]
				s = s[0..f1]+"<a href=~q***"+link+"~q>"+link+"</a>"+s[f2..s.length]
			EndIf
		Until f1<0
		
		'
		' bbdoc @something = Parameter description
		Repeat
			f1 = s.find("@")
			f2 = s.length
			If f1=>0
				For i = f1 Until s.length
					If s[i]<=32 Or s[i]=Asc(",") Or s[i]=Asc(".") Or s[i]=Asc(")") Or s[i]=Asc("(")
						f2 = i
						Exit
					EndIf
				Next
				link:String = s[f1+1..f2]
				s = s[0..f1]+"<font class=~qfuncparam~q>"+link+"</font>"+s[f2..s.length]
			EndIf
		Until f1<0
	EndIf
	
	Return s
	
End Function

'
' Fixes up HTML files to look nicer
Function ReformatHTML:String(s:String,isIntro:Int=False)
	
	' Remove header
	s = RemoveSection(s,"<head>","</head>")

	'
	' Fix tables
	s = ReplaceSection(s,"<table",">","<table class=~qsubtable~q>")
	s = ReplaceSection(s,"<th",">","<th class=~qsubth~q>")
	s = ReplaceSection(s,"<td",">","<td class=~qsubtd~q>")

	If isIntro
		' Remove original title
		s = RemoveSection(s,"<h1>","</h1>")
		'
		' Add some stuff :)
		s = "<h2>Introduction</h2>"+s
	Else
		'
		' Fix title
		s = s.Replace("<h1>","<tr><td class=~qmodulename~q>")
		s = s.Replace("</h1>","</td></tr><tr><td class=~qmoduleintro~q>")
	EndIf
		
	' Remove html and body tags
	s = s.Replace("<html>","")
	s = s.Replace("</html>","")
	s = s.Replace("<body>","")
	s = s.Replace("</body>","")
	s = s.Trim()	

	'
	' We don't like <p>
	s = s.Replace("<p>","<br><br>")
	s = s.Replace("</p>","<br><br>")
	
	'
	' Replace styling
	s = s.Replace("class=syntax","class=~qfuncparam~q")
	s = s.Replace("class=token","class=~qfuncname~q")
		
	'
	' Fix headings
	s = s.Replace("<h2>","</td></tr><tr><td class=~qmoduleintrohead~q>")
	s = s.Replace("</h2>","</td></tr><tr><td class=~qmoduleintro~q>")
		
	Return s
	
End Function

'
' Fixes up navbar.html to look nicer
Function ReformatNavbar:String(s:String)

	' Remove header
	s = RemoveSection(s,"<head>","</head>")

	' Remove html and body tags
	s = s.Replace("<html>","")
	s = s.Replace("</html>","")
	s = s.Replace("<body>","")
	s = s.Replace("</body>","")
	s = s.Trim()
	
	' Replace <bold> tag
	s = s.Replace("<b>","<div class=~qnav~q>")
	s = s.Replace("</b>","</div>")
	
	' Remove <br> tags
	s = s.Replace("<br>","")
	
	' fix styles
	s = s.Replace("class=null","class=~qnavmodlink~q")
	s = s.Replace("<a","<div class=~qnavmod~q><a")
	s = s.Replace("</a>","</a></div>")

	Return s
	
End Function

'
' Removes a section of string 's' between 'a' and 'b'.
' If 'all' is true 'a' and 'b' is removed as well.
Function RemoveSection:String(s:String,a:String,b:String,all:Int=True)
	
	Local f1 = s.find(a)
	If f1=>0
		Local f2 = s.find(b,f1)
		If f2=>0
			If all
				s = s[0..f1]+s[f2+b.length..s.length]
			Else
				s = s[0..f1+a.length]+s[f2..s.length]
			EndIf
		EndIf
	EndIf
	
	Return s
	
End Function

Function ReplaceSection:String(s:String,a:String,b:String,repl:String)
	
	Local f1 = 0
	While f1=>0
		f1 = s.find(a,f1)
		If f1=>0
			Local f2 = s.find(b,f1)
			If f2=>0
				s = s[0..f1]+repl+s[f2+b.length..s.length]
			EndIf
			f1:+1
		EndIf
	Wend
	
	Return s
	
End Function 

'
' Fixes html links to functions/commands
Function FixFunctionRefs:String(s:String,m:dmod)

	Local f1 = 0
	While f1=>0
			
		f1 = s.find("***",f1)
		If f1=>0
			Local f2 = s.find("~q",f1+3)
			If f2=>0
				Local funname:String = s[f1+3..f2]
				Local found = False
				'
				' Check the same mod first
				For ff:dfunc = EachIn m.funclist
					If Lower(ff.sname) = Lower(funname)
						s = s[0..f1]+"#"+ff.sname+s[f2..s.length]
						found = True
					EndIf
				Next
					
				If found = False	
					'
					' Check other mods, if the reference is not within the same mod
					For ff:dfunc = EachIn funlist
						If Lower(ff.sname) = Lower(funname)
							relFile:String = GetRelativeFilename(ExtractDir(m.docfile),ff.mymod.docfile)
							s = s[0..f1]+relFile+"#"+ff.sname+s[f2..s.length]
						EndIf
					Next	
				EndIf				
			EndIf
			f1 = f1+1
		EndIf
				
	Wend
	
	Return s
	
End Function

'
' Finds Example source files for commands, and intro.html files
' if they exist for the different modules and functions.
Function FindExamples(modsfolder:String)
	
	For m:dmod = EachIn modlist
		Local intro:String = Lower(ExtractDir(m.file)+"/doc/intro.html")
		Local stream = ReadFile(intro)
		If stream
			Local size = StreamSize(stream)
			If size>0
				m.intro = ReadString(stream,size)
				m.intro = ReformatHTML(m.intro,True)
				Print m.name+" -> introduction found!"
			EndIf
			CloseFile stream
		EndIf
		
		FlushMem		
	Next
	
	For f:dfunc = EachIn funlist
		Local example:String = Lower(ExtractDir(f.mymod.file)+"/doc/"+f.sname+".bmx")
		stream = ReadFile(example)
		If stream
			size = StreamSize(stream)
			If size>0
				f.exfile = example
				f.example = ReadString(stream,size)
				Print f.sname+" -> example found!"
			EndIf
			CloseFile stream
		EndIf
		
		FlushMem
	Next
	
End Function

'
' Writes the module documents
Function WriteModDocs()
	
	Local stream
	Local modsfolder:String = bmaxfolder+"mod"
	Local outfolder:String = destfolder+"doc/bmxmods"
	
	outfolder = StripSlash(outfolder)
	
	If FileType(outfolder) = 0
		Assert CreateDir(outfolder),"Couldn't create destination folder!"
	EndIf
	
	Print "Scanning for modules..."
	ScanModFolder(modsfolder)
	
	Print "Scanning for examples..."
	FindExamples(modsfolder)
		
	For m:dmod = EachIn modlist
		m.docfile:String = Lower(ExtractDir(m.file)+"/doc/commands.html")
	Next
	
	'
	' Remove commands with no documentation
	If SKIP_NONDOCED
		For f:dfunc = EachIn funlist
			If f.rets.length = 0 And f.desc.length = 0 And f.about.length = 0
				ListRemove(f.mymod.funclist,f)
				ListRemove(funlist,f)
			EndIf
		Next
	EndIf
	
	'
	' Sort out cross references
	For f:dfunc = EachIn funlist
		f.about = FixFunctionRefs(f.about,f.mymod)
	Next
	For m:dmod = EachIn modlist
		m.intro = FixFunctionRefs(m.intro,m)
	Next

	modlist.sort

	Print modlist.count()+" Modules found!"

	Print "Writing Commands.txt..."
	stream = WriteFile(bmaxfolder+"doc/bmxmods/commands.txt")
	If stream
		For f:dfunc = EachIn funlist
			
			relFile:String = f.mymod.docfile'GetRelativeFilename(bmaxfolder,f.mymod.docfile)
			relFile = relFile[Len(StripSlash(bmaxfolder))..]
			If f.keyword = False
				Print "RELFILE:"+relFile
				WriteLine(stream,f.name+"("+f.param+")|"+relFile+"#"+f.sname)
			Else
				WriteLine(stream,f.name+"|"+relFile+"#"+f.sname)
			EndIf
		Next
		CloseFile stream
	Else
		Notify "Couldn't write commands.txt"
	EndIf

	Print "Writing Module Documents..."
		
	'
	' Write module doc files
	For m:dmod = EachIn modlist
		
		destdir:String = ExtractDir(m.docfile)
		If FileType(destdir) = 0
			If CreateDir(destdir) = False
				Notify("Couldn't create 'doc/' folder for module:~n~q"+m.name+"~q")
			EndIf
		EndIf
		
		stream = WriteFile(m.docfile)
		
		If stream
			' html header
			WriteLine(stream,"<html>")
			WriteLine(stream,"<head>")
			relFile:String = GetRelativeFilename(ExtractDir(m.docfile),bmaxfolder+"doc/bmxmods/docs.css")
			WriteLine(stream,"<link rel=styleSheet href=~q"+relFile+"~q type='text/css'>")
			WriteLine(stream,"</head>")
			WriteLine(stream,"<body class=~qbody~q>")

			' module name
			WriteLine(stream,"<table width=100% class=~qbody~q>")
			WriteLine(stream,"<tr><td class=~qmodulename~q><a href=~qfile://" + m.file +"~q> "+m.name+"</a></td></tr>")
				
			' module intro
			WriteLine(stream,"<tr><td class=~qmoduleintro~q>"+m.intro+"</td></tr>")
				
			' module functions
			If m.funclist.count()>0
				m.funclist.sort
				WriteLine(stream,"<tr><td class=~qbody~q>")
				For f:dfunc = EachIn m.funclist
					WriteLine(stream,"<table class=~qfunctionbox~q><tr><td class=~qfunchead~q colspan=2 id=~q"+f.sname+"~q>")
					If f.keyword = False
						WriteLine(stream,f.name+"(<font class=~qfuncparam~q>"+f.param+"</font>)")
					Else
						WriteLine(stream,f.name)
					EndIf
					WriteLine(stream,"</td></tr>")

					If f.rets.length 
						WriteLine(stream,"<tr><td class=~qfuncleft~q>Returns</td>")
						WriteLine(stream,"<td class=~qfuncright~q>"+f.rets+"</td></tr>")
					EndIf
						
					If f.desc.length
						WriteLine(stream,"<tr><td class=~qfuncleft~q>Short description</td>")
						WriteLine(stream,"<td class=~qfuncright~q>"+f.desc+"</td></tr>")
					EndIf
						
					If f.about.length
						WriteLine(stream,"<tr><td class=~qfuncleft~q>Long description</td>")
						WriteLine(stream,"<td class=~qfuncright~q>"+f.about+"</td></tr>")
					EndIf
						
					If f.example.length
						WriteLine(stream,"<tr><td class=~qfuncleft~q>Example</td>")
						relFile:String = GetRelativeFilename(ExtractDir(m.docfile),f.exfile)
						WriteLine(stream,"<td class=~qfuncright~q><a href=~q"+relFile+"~q target=~q_blank~q class=~qexample~q><pre>"+f.example+"</pre></a></td></tr>")
					EndIf
					'
					' Remember to fetch examples???
						
					WriteLine(stream,"</table><br>")
				Next
				WriteLine stream,"</td></tr>"
			EndIf
				
			' module info
			WriteLine(stream,"<tr><td class=~qbody~q>")
			Local out:String = "<table class=~qmodinfobox~q>"+linefeed+"<tr><td colspan=2 class=~qmodinfohead~q>Module Info</tr></td>"+linefeed+m.info+linefeed+"</table>"
			WriteLine(stream,out)		
			WriteLine(stream,"</td></tr></table>")
			WriteLine(stream,"</body>")
			WriteLine(stream,"</html>")
			CloseFile stream
		Else
			Notify "Couldn't create output file:~n~q"+m.docfile+"~q"
		EndIf
	
	Next
	
	Print "Writing Navigation Document..."
		
	'
	' Write navigation file
	Local navfile:String = bmaxfolder+"doc/bmxmods/navbar.html"
	stream = WriteFile(navfile)
	If stream

		' html header
		WriteLine(stream,"<html>")
		WriteLine(stream,"<head>")
		WriteLine(stream,"<link rel=styleSheet href=~qdocs.css~q type='text/css'>")
		WriteLine(stream,"<script>")
		WriteLine(stream,"// Do we use DOM or not?")
		WriteLine(stream,"var dom = (document.getElementById && !document.all)? 1: 0;")
		WriteLine(stream,"function toggle(the_id)")
		WriteLine(stream,"{")
		WriteLine(stream,"	var obj = (dom)? document.getElementById(the_id): document.all[the_id];")
		WriteLine(stream,"	if(obj.style.display == 'inline'){")
		WriteLine(stream,"		obj.style.visibility = 'hidden';")
		WriteLine(stream,"		obj.style.display = 'none';")
		WriteLine(stream,"	}else{")
		WriteLine(stream,"		obj.style.visibility = 'visible';")
		WriteLine(stream,"		obj.style.display = 'inline';")
		WriteLine(stream,"	}")
		WriteLine(stream,"}")
		WriteLine(stream,"</script>")
		WriteLine(stream,"</head>")
		WriteLine(stream,"<body>")

		' module list
		WriteLine(stream,"<table width=~q200px~q><tr><td>")
		WriteLine(stream,"<div class=~qnav~q>By Module</div>")
		For m:dmod = EachIn modlist
			Local id:String = m.name.Replace(".","_")
			Local first = True
			For f:dfunc = EachIn m.funclist
				If first
					relFile:String = GetRelativeFilename(ExtractDir(navfile),m.docfile)
					WriteLine(stream,"<div class=~qnavmod~q><a class=~qnavmodlink~q onClick=~qtoggle('"+id+"')~q href=~q"+relFile+"~q target=~qmain~q>"+m.name+"</a></div>")
					WriteLine(stream,"<div id=~q"+id+"~q class=~qnavfunclist~q>")
					first = False
				EndIf
				WriteLine(stream,"<div class=~qnavfunc~q><a class=~qnavfunclink~q href=~q"+relFile+"#"+f.sname+"~q target=~qmain~q>"+f.sname+"</a></div>")
			Next
			If first = False
				WriteLine(stream,"</div>")
			EndIf
		Next
		WriteLine(stream,"</td></tr><tr><td>")
			
		' alphabetical function list
		WriteLine(stream,"<div class=~qnav~q><br>Alphabetical</div>")
		funlist.sort
		alp:String = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ"
		For Local char:Int = 0 Until alp.length
			s:String = Chr(alp[char])
			id:String = "Alpha_"+s
			first = True
			For f:dfunc = EachIn funlist
				If Upper(Chr(f.sname[0]))=s
					If first
						WriteLine(stream,"<div class=~qnavmod~q><a class=~qnavmodlink~q href=~qjavascript:toggle('"+id+"')~q>"+s+"</a></div>")
						WriteLine(stream,"<div id=~q"+id+"~q class=~qnavfunclist~q>")
						first = False
					EndIf
					relFile:String = GetRelativeFilename(ExtractDir(navfile),f.mymod.docfile)
					WriteLine(stream,"<div class=~qnavfunc~q><a class=~qnavfunclink~q href=~q"+relFile+"#"+f.sname+"~q target=~qmain~q>"+f.sname+"</a></div>")
				EndIf
			Next
			If first = False
				WriteLine(stream,"</div>")
			EndIf
		Next
		WriteLine(stream,"</td></tr></table>")
		WriteLine(stream,"</body>")
		WriteLine(stream,"</html>")
			
		CloseFile stream
	Else
		Notify "Couldn't create output file:~n~q"+navfile+"~q"
	EndIf
		
	WriteIndexFile(bmaxfolder+"doc/bmxmods/index.html")
		
	GenerateCSSFile(outfolder)
	
End Function

'
' Write index.html file
Function WriteIndexFile(indfile:String)

	Print "Writing Index Document..."
	stream = WriteFile(indfile)
	If stream
		WriteLine(stream,"<html><head>")
		WriteLine(stream,"<title>BlitzMax Command Reference</title>")
		WriteLine(stream,"</head>")
		WriteLine(stream,"<frameset cols='230,*'>")
		WriteLine(stream,"<frame src=~qnavbar.html~q name=~qnavbar~q><frame src=~qwelcome.html~q name=~qmain~q>")
		WriteLine(stream,"</frameset>")
		WriteLine(stream,"</html>")
		CloseFile stream
	Else
		Notify "Couldn't create output file:~n~q"+indfile+"~q"
	EndIf
	
End Function

'
' Converts non module documents to the same style
Function ConvertDocs()

	Local checkfolder:String[] = [bmaxfolder+"doc/bmxmods",bmaxfolder+"doc/bmxlang",bmaxfolder+"doc/bmxuser"]	
	
	Local stream
	Local size
	Local folder:String
	Local files:String[]
	Local file:String
	Local fi:String
	Local s:String

	For folder = EachIn checkfolder
		'
		' Backup original html files
		BackupFolder(folder,["htm","html"])

		files = LoadDir(folder,True)

		For fi = EachIn files
			
			file = folder+"/"+fi
			
			If FileType(file)=1
				'
				' This file is an original html file that's
				' been backed up. We use that to generate the new
				' docs.
				If file.find(backuppre)=>0 

					Local f1:Int = file.find(backuppre)
					Local targetfile:String = file[0..f1]+file[f1+backuppre.length..file.length]

					stream = ReadFile(file)
					If stream
						size = StreamSize(stream)
						If size>0
							s:String = ReadString(stream,size)
						EndIf
						CloseFile stream

						If targetfile.find("navbar")=>0
							' Navigation bar
							s = ReformatNavbar(s)

							stream = WriteFile(targetfile)
							If stream
								' html header
								WriteLine(stream,"<html>")
								WriteLine(stream,"<head>")
								WriteLine(stream,"<link rel=styleSheet href=~qdocs.css~q type='text/css'>")
								WriteLine(stream,"<script>")
								WriteLine(stream,"// Do we use DOM or not?")
								WriteLine(stream,"var dom = (document.getElementById && !document.all)? 1: 0;")
								WriteLine(stream,"function toggle(the_id)")
								WriteLine(stream,"{")
								WriteLine(stream,"	var obj = (dom)? document.getElementById(the_id): document.all[the_id];")
								WriteLine(stream,"	if(obj.style.display == 'inline'){")
								WriteLine(stream,"		obj.style.visibility = 'hidden';")
								WriteLine(stream,"		obj.style.display = 'none';")
								WriteLine(stream,"	}else{")
								WriteLine(stream,"		obj.style.visibility = 'visible';")
								WriteLine(stream,"		obj.style.display = 'inline';")
								WriteLine(stream,"	}")
								WriteLine(stream,"}")
								WriteLine(stream,"</script>")
								WriteLine(stream,"</head>")
								WriteLine(stream,"<body>")
	
								' nav list
								WriteLine(stream,"<table width=~q200px~q><tr><td>")
									
								WriteLine(stream,s)
					
								WriteLine(stream,"</td></tr></table>")
								WriteLine(stream,"</body>")
								WriteLine(stream,"</html>")
								
								CloseFile stream 
							Else
								Notify "Unable to write file:~n~q"+targetfile+"~q!",True
								End
							EndIf							
						Else
							' Regular html doc
							s = ReformatHTML(s)

							stream = WriteFile(targetfile)
							If stream
								' html header
								WriteLine(stream,"<html>")
								WriteLine(stream,"<head>")
								WriteLine(stream,"<link rel=styleSheet href=~qfile://"+folder+"/docs.css~q type='text/css'>")
								WriteLine(stream,"</head>")
								WriteLine(stream,"<body class=~qbody~q>")
								WriteLine(stream,"<table width=95% class=~qbody~q>")
								WriteLine(stream,"<tr><td class=~qmoduleintro~q>")
								WriteString(stream,s)
								WriteLine(stream,"</td></tr></table>")
								WriteLine(stream,"</body>")
								WriteLine(stream,"</html>")
								CloseFile stream
							Else
								Notify "Unable to write file:~n~q"+targetfile+"~q!",True
								End
							EndIf
						EndIf
					EndIf
				EndIf
			EndIf
		Next
				
		GenerateCSSFile(folder)
		WriteIndexFile(folder+"/index.html")
	Next
	
End Function

'
' Backups all files with certain extensions in a folder
Function BackupFolder(folder:String,extensions:String[])
	
	Local backitup:Int
	Local fi:String
	Local file:String
	Local ext:String
	Local files:String[]
	files = LoadDir(folder,True)

	For fi = EachIn files
			
		file = folder+"/"+fi
			
		If FileType(file)=1
			If file.find(backuppre)<0 

				backitup = False
				
				If extensions.length = 0
					backitup = True
				EndIf
				
				For ext = EachIn extensions
					If Lower(ExtractExt(file)) = ext
						backitup = True
					EndIf
				Next
				
				If backitup
					If BackupFile(file)
						Print "Backing up: ~q"+file+"~q..."
					EndIf
				EndIf
			EndIf
		EndIf
		
		FlushMem
	Next
		
End Function

'
' Backups a single file, only if a backup doesn't already exist,
' unless the force flag is set
Function BackupFile(file:String,force:Int=False)

	Local backupfile:String = ExtractDir(file)+"/"+backuppre+StripDir(file)

	If FileType(backupfile) = 0 Or force = True
		Local stream = ReadFile(file)
		If stream
			Local size = StreamSize(stream)
			If size>0
				Local s:String = ReadString(stream,size)
			EndIf
			CloseFile stream

			stream = WriteFile(backupfile)
			If stream
				WriteString(stream,s)
				CloseFile stream
			Else
				Notify "Unable to backup file:~n~q"+file+"~q!",True
				End
			EndIf
		Else
			Return False
		EndIf
	Else
		Return False
	EndIf

	Return True

End Function

'
' Creates a CSS file containing a style sheet
Function GenerateCSSFile(outfolder:String)

	Print "Writing CSS Document..."
	Local c:CSS_Class
	
	ClearList(CSS_List)
	
	Local cssfile:String = outfolder+"/docs.css"
	stream = WriteFile(cssfile)
	If stream
		c:CSS_Class = New CSS_Class
		c.name 			= ".body"
		c.background 	= "#FFFFFF"
		c.border		= "0px"
		c.font			= "10pt helvetica"
		c.width			= "100%"
		
		c:CSS_Class = New CSS_Class
		c.name			= ".modulename"
		c.font			= "20pt helvetica"
		c.background	= "#FFFFFF"
		c.border_bottom	= "20px solid #FFFFFF"
		c.width			= "100%"

		c:CSS_Class = New CSS_Class
		c.name			= ".moduleintro"
		c.font			= "10pt helvetica"
		c.background	= "#FFFFFF"
		c.border_bottom = "20px solid #FFFFFF"
		c.padding_left	= "20px"
		c.width			= "100%"

		c:CSS_Class = New CSS_Class
		c.name			= ".moduleintrohead"
		c.font			= "15pt helvetica"
		c.padding_left	= "0px"
			
		c:CSS_Class = New CSS_Class
		c.name			= ".functionbox"
		c.padding_top	= "20px"
		c.font			= "10pt helvetica"
		c.width			= "100%"
						
		c:CSS_Class = New CSS_Class
		c.name			= ".funchead"
		c.padding		= "10px"
		c.background	= "#CCDDEE"
		c.border_bottom	= "1px Solid #BBCCDD"
		c.font_weight	= "bold"
		c.width			= "100%"

		c:CSS_Class = New CSS_Class
		c.name			= ".funcname"
		c.font_style	= "normal"
		c.font_weight	= "bold"
			
		c:CSS_Class = New CSS_Class
		c.name			= ".funcparam"
		c.font_style	= "italic"
		c.font_weight	= "normal"

		c:CSS_Class = New CSS_Class
		c.name			= ".funcleft"
		c.font_weight	= "normal"
		c.padding		= "10px"
		c.font			= "8pt helvetica"
		c.background	= "#CCDDEE"
		c.border_bottom	= "1px solid #BBCCDD"
		c.width			= "15%"
		c.v_align		= "text-top"
			
		c:CSS_Class = New CSS_Class
		c.name			= ".funcright"
		c.font			= "10pt helvetica"
		c.font_style	= "normal"
		c.font_weight	= "normal"
		c.background	= "#DDEEFF"
		c.border_bottom	= "1px solid #CCDDEE"
		c.padding		= "10px"
		c.width			= "85%"
						
		'
		'
		' Module info box
		c:CSS_Class = New CSS_Class
		c.name			= ".modinfobox"
		c.width			= "100%"

		c:CSS_Class = New CSS_Class
		c.name			= ".modinfohead"
		c.background	= "#CCDDEE"
		c.border_bottom	= "1px solid #BBCCDD"
		c.font			= "8pt helvetica"
		c.padding		= "10px"
		c.font			= "10pt helvetica"
		c.font_weight	= "bold"
		c.width			= "100%"
			
		c:CSS_Class = New CSS_Class
		c.name			= ".modinfoleft"
		c.background	= "#CCDDEE"
		c.border_bottom	= "1px solid #BBCCDD"
		c.font			= "8pt helvetica"
		c.padding_left	= "10px"
		c.width			= "15%"

		c:CSS_Class = New CSS_Class
		c.name			= ".modinforight"
		c.background	= "#DDEEFF"
		c.font			= "8pt helvetica"
		c.border_bottom	= "1px solid #CCDDEE"
		c.padding_left	= "10px"
		c.width			= "85%"
			
		'
		'
		' Navigation Bar
		c:CSS_Class = New CSS_Class
		c.name			= ".nav"
		c.font			= "10pt helvetica"
		c.width			= "100%"
		c.border_top	= "2px solid #FFFFFF"
		c.padding		= "0px"
			
		c:CSS_Class = New CSS_Class
		c.name			= ".navfunclist"
		c.display		= "none"
		c.visibility	= "hidden"
		c.font			= "10pt helvetica"
		c.border		= "0px"
						
		c:CSS_Class = New CSS_Class
		c.name			= ".navmod"
		c.background	= "#CCDDEE"
		c.border_top	= "2px solid #FFFFFF"
		c.border_bottom	= "1px solid #BBCCDD"
		c.font			= "10pt helvetica"
		c.padding_left	= "10px"
			
		c:CSS_Class = New CSS_Class
		c.name			= ".navfunc"
		c.padding_left	= "20px"
		c.background	= "#DDEEFF"
		c.border_top	= "2px solid #FFFFFF"
		c.border_bottom	= "1px solid #CCDDEE"
		
		'
		' Table stuff
		c:CSS_Class = New CSS_Class
		c.name				= ".subtable"
		c.font				= "10pt helvetica"
		c.background		= "#FFFFFF"
		
		c:CSS_Class = New CSS_Class
		c.name				= ".subth"
		c.font				= "10pt helvetica"
		c.font_weight		= "bold"
		c.text_align		= "left"
		c.background		= "#CCDDEE"
		c.border_bottom		= "1px solid #BBCCDD"
		c.padding_left		= "10px"
		c.padding_right		= "10px"
			
		c:CSS_Class = New CSS_Class
		c.name				= ".subtd"
		c.font				= "10pt helvetica"
		c.font_weight		= "normal"
		c.text_align		= "left"
		c.background		= "#DDEEFF"
		c.border_top		= "0px solid #FFFFFF"
		c.border_bottom		= "1px solid #CCDDEE"
		c.padding_left		= "10px"
		c.padding_right		= "10px"
			
		'
		' Hyperlink stuff
		c:CSS_Class = New CSS_Class
		c.name				= "a"
		c.color				= "#000000"

		c:CSS_Class = New CSS_Class
		c.name				= "a:hover"
		c.color				= "#445566"

		c:CSS_Class = New CSS_Class
		c.name				= "a.navmodlink"
		c.font_weight		= "bold"
		c.width				= "100%"
		c.text_decoration	= "none"
			
		c:CSS_Class = New CSS_Class
		c.name				= "a.navfunclink"
		c.text_decoration	= "none"
		c.width				= "100%"

		c:CSS_Class = New CSS_Class
		c.name				= "a.example"
		c.color				= "#000000"
		c.text_decoration	= "none"
		c.width				= "100%"

		c:CSS_Class = New CSS_Class
		c.name				= "pre"
		c.padding			= "10px"
		c.background		= "#EEF8FF"
		
		For c:CSS_Class = EachIn CSS_List
			c.Write(stream)
		Next
		CloseFile stream
	Else
		Notify "Couldn't create output file:~n~q"+cssfile+"~q"
	EndIf
	
End Function

'
' CSS_Class type, utillity class to ease writing of css files
Type CSS_Class

	Field name:String
	Field display:String
	Field background:String
	Field cell_spacing:String
	Field border:String
	Field border_bottom:String
	Field border_left:String
	Field border_top:String
	Field border_right:String
	Field color:String
	Field padding:String
	Field padding_left:String
	Field padding_right:String
	Field padding_top:String
	Field padding_bottom:String
	Field font:String
	Field font_style:String
	Field font_weight:String
	Field text_decoration:String
	Field width:String
	Field max_width:String
	Field min_width:String
	Field height:String
	Field max_height:String
	Field min_height:String
	Field v_align:String
	Field text_align:String
	Field visibility:String
		
	Method New()
		CSS_List.addlast Self
	End Method
	
	Method Write(stream)
		WriteLine(stream,Self.name+" {")
		If Self.visibility.length		Then WriteLine(stream,"  visibility: "+Self.visibility+";")
		If Self.display.length			Then WriteLine(stream,"  display: "+Self.display+";")
		If Self.color.length			Then WriteLine(stream,"  color: "+Self.color+";")
		If Self.font.length				Then WriteLine(stream,"  font: "+Self.font+";")
		If Self.font_style.length		Then WriteLine(stream,"  font-style: "+Self.font_style+";")
		If Self.font_weight.length		Then WriteLine(stream,"  font-weight: "+Self.font_weight+";")
		If Self.text_decoration.length	Then WriteLine(stream,"  text-decoration: "+Self.text_decoration+";")		
		If Self.width.length			Then WriteLine(stream,"  width: "+Self.width+";")		
		If Self.max_width.length		Then WriteLine(stream,"  max-width: "+Self.max_width+";")
		If Self.min_width.length		Then WriteLine(stream,"  min-width: "+Self.min_width+";")
		If Self.height.length			Then WriteLine(stream,"  height: "+Self.height+";")	
		If Self.max_height.length		Then WriteLine(stream,"  max-height: "+Self.max_height+";")	
		If Self.min_height.length		Then WriteLine(stream,"  min-height: "+Self.min_height+";")	
		If Self.background.length		Then WriteLine(stream,"  background: "+Self.background+";")
		If Self.border.length			Then WriteLine(stream,"  border: "+Self.border+";")
		If Self.border_bottom.length	Then WriteLine(stream,"  border-bottom: "+Self.border_bottom+";")
		If Self.border_left.length		Then WriteLine(stream,"  border-left: "+Self.border_left+";")
		If Self.border_right.length		Then WriteLine(stream,"  border-right: "+Self.border_right+";")
		If Self.border_top.length		Then WriteLine(stream,"  border-top: "+Self.border_top+";")
		If Self.padding.length			Then WriteLine(stream,"  padding: "+Self.padding+";")
		If Self.padding_bottom.length	Then WriteLine(stream,"  padding-bottom: "+Self.padding_bottom+";")
		If Self.padding_left.length		Then WriteLine(stream,"  padding-left: "+Self.padding_left+";")
		If Self.padding_right.length	Then WriteLine(stream,"  padding-right: "+Self.padding_right+";")
		If Self.padding_top.length		Then WriteLine(stream,"  padding-top: "+Self.padding_top+";")
		If Self.cell_spacing.length		Then WriteLine(stream,"  cell-spacing: "+Self.cell_spacing+";")
		If Self.v_align.length			Then WriteLine(stream,"  vertical-align: "+Self.v_align+";")
		If Self.text_align.length		Then WriteLine(stream,"  text-align: "+Self.text_align+";")

		WriteLine(stream,"}")
		WriteLine(stream,"")
	End Method
	
End Type


'
' Convert normal docs :)
ConvertDocs()

'
' Write the Module docs :)
WriteModDocs()

Print "Finished!"