Documenta

Miscellaneous Forums/Blitz Showcase/Documenta

Just a little something to generate nicer module documentation. It's not done yet, so you'll see some stuff missing. Use it if you like, and feel free to add to it.

It eats your mod folder and spits out a document folder :)

Warning: It overwrites your original BlitzMax Command Reference. Use at your own risk!

'
' 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!"



It spits out html files, looking something like this:

Have Fun!

cool :) the menu page could do with a bit of tidying.

Yep, it's not done yet :)

just need to add a <BR> after the </span>

way cool :-).

clever

Added some extra bits, so intro.html files are automatically included, and examples as well. There might be a few minor bugs with the intro.html, because it's being reformatted, and it might mess up a few links.

Hey, I like this - do me one too

Nice work fred.

Nicely done! Like Doxygen for Blitz! You rule Fred!! Works fine on the Mac. Had to fiddle with the directory structure of course but minimal hassle!

best thing i have seen for blitzmax good work mate

damned, it's really good :)
ok with ie, but i use FireFox and pb with navbar, it doesn't expand :(

It's way better than the existing help files!

Perhaps BRL would do well to use this style. It can only improve the product...

*EDIT*
Never mind, I messed up.

However, the fancy drop down css doesn't work in anything except IE. :\

I take that back, it works in Konquerer as well.

awesome. Mark, notice :)

It doesn't work correctly in FireFox at the moment, I'll try and figure out why later (I know that FF does support the display property in CSS)

Updated! It now works with FireFox (and most over browsers I assume).

And it now overwrites your original BlitzMax command reference, so that you can use F1 to bring it up inside the IDE.

Please make sure to back up the original documentation before running it!

Use at your own risk!


How to enter yes, or no? I cant hit any buttons when the question come, overwrite or not. Confirm does not work for me :( .

On linux or Windows? It works for me on windows...

[Edit] Try copying the source over again, I changed the confirm alert to a proceed alert...If it still doesn't work, just remove the alert.

Again : Damned, it's really good
thanks for the firefox's fix :)

REALLY COOL! :D

ty!

great work fredborg !

Quite stunning.

OOOOH great job fredborg!!! If I finish my bmax parser (atm grapling with making a fast uber split function), you can use it for highlighting in the samples.

Absolutely great! Looks good in Firefox over here. Thanks Fredborg.

feature suggestion
Have documenta overwrite the other help files as well and combine with www.blitzwiki.org so it comprehensively updates.

The idea is that if you do a syncmods, you can also do a syncdocs, which will run the program, fix the docs and also take the stuff from the blitzwiki project online.

This will mean very powerful local docs, but it may be really hard to do.

It's worth thinking about though.

Todd is the guy who set up the Wiki, and he might know enough about it to get fredborg's output into a Wiki format. That would be excellent.

Aaron

I get:

"C:/Programmer/Coding/BlitzMax/Samples/documenta.debug"
Unhandled Exception: Couldn't create destination folder!
WriteDocs [f:\blitzmax\mod\brl.mod\filesystem.mod/filesystem.bmx;82;41]
documenta [C:\Programmer\Coding\BlitzMax\Samples/documenta.bmx;1062;1]

I set the Const bmaxfolder:String = "C:/Programmer/Coding/BlitzMax"

[EDIT] also tried : "C:\Programme\Coding\BlitzMax"

I don't have an f drive.

"C:/Programmer/Coding/BlitzMax/" made it work

Very Nice Work, Fredborg!

skunk - that could be possible, even applicable since the pages are stored as xml, and it would be possible to scan through it for the headers and if it is following the rules of construction make it into the standard formats. For pages not in the correct form, the default bb docs could be used, and the page added to a list of non-compliant pages.

[edit]Actually, you can view this xml by going to http://www.blitzwiki.org/index.php/Special:Export , and entering the pages you want to view. for instance "ResizeBank"

This isnt actually what i was expecting, i was hoping for more of a xhtml representation of the actiol contents but ohwell. it could be possible if something like the wikipedia database dump could be created:

http://download.wikimedia.org/

Updated to convert all help files including Language Reference and User Guide.

It also creates backups of all the documents, except the individual module documents. These are automatically regenerated if you run 'BMK makedocs'.

I will add a rollback option, so you may revert to the original documents.

got an error about the bmax folder variable being const, changing to global solves it obviously.

Strange, bmaxfolder is a global :)

oops, I think it was from the old code (I kept my bmaxfolder variable and never noticed it had changed to global) :)

great, nice work
just one thing, about alphabetical order ( From A to Z :) )
What about functions starting with "_" like "_cls()" ?

Looks much better than the original.
But some of the docs in the /mod/pub are not converted...

Mipooh

Is there anyway to get max to document your own projects ??

Yavin: 'bmk docmods' in the commandline. It doesn't document types or methods though, so if you want that you're out of luck.

Hey rad! Thanks for that tip! I was wondering the same thing Yavin! Thanks Noel!

Add it to your sig, fredborg!

bombs out on
release f

subexpression for release must be int blah etc

if you've updated to the 1.03 version, you need to change those lines to f=null, etc

Hi,

It should work with 1.03 now, and also with the official BMax IDE. At least on windows :)

It does indeed :)

*EDIT*
Hmmm... quickhelp is giving me problems.

---------------------------
Microsoft Internet Explorer
---------------------------
Cannot find 'C:/BlitzMaxBeta101/doc/bmxmods/c:/blitzmaxbeta101/mod/brl.mod/filesystem.mod/doc/commands.html#FileType'.
 Make sure the path or Internet address is correct.
---------------------------
OK   
---------------------------


*EDIT*
deleting commands.txt and redoing it seemed to work.

awesome work, that looks so much more professional now, well done

Cool, thanks! Works nicely in the Mac editor as well.

in function WriteModDocs

change the line that writes the module name to:
			WriteLine(stream,"<tr><td class=~qmodulename~q><a href=~qfile://" + m.file +"~q> "+m.name+"</a></td></tr>")

You can then click the module name to open the module source :)

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!"



Cool Perturbatio, I've updated the top post as well :)

Cool!

works like a charm and a real improvement. Something for BRL to consider to make this the default look?

The one thing I've noticed is that the 1.09 IDE doesn't populate the navbar correctly when using documenta's navbar html, it works fine with the old one.

Looks great, but can you have it Generate an Index like the original has? I use that A LOT.