The point is a brief overview of script functionality and components at a moments glance such as all functions with their required params,that is the extent and purpose of this 'documentation' generator, and at the very least it's html output can be used as a base for a proper documentation.
Usage: Drag & Drop script(S) for which to generate docs/overview, what is generated is dependent on how well commented the script is, see examples attached.
See Attached Examples of generated docs for some widely used scripts & libraries. Gdip,StringThings,VA & BB.html aptly demonstrate the purpose of this script.
Additional stats:
- Non-Blank & Uncommented Number of Lines,i.e actual lines of code, centered under script description.
Number of Function/Label calls, given showFunction_LabelCallCounts=True *default False.
Spoiler
FYI: Yes i'm aware of GenDocs by fincs, i actually wrote this because i found it difficult to use,and i'm too lazy to write docs, but as we all must, i comment as i must,hence...Edit: Fixed Issues Mentioned by helgef in first reply to post. & Updated Examples accordingly.
Code: Select all
;v1.1
;
; AutoHotkey: v1.1.26.01 Unicode 32-bit (Installed)
; Language: English
; OS: WIN_7 64-bit Service Pack 1 v6.1.7601 (WIN32_NT) , 6050.68MB RAM
; Author: brutus_skywalker
; Date: Saturday, June 09, 2018
;
/*
Usage: Drag & Drop Other AHK scripts on to DocGen.ahk, html 'documentation' should be generated in the Drag & Dropped script dir with the corresponding file name.
I'm Aware of the GenDoc script by fincs... This script's purpose is to 'document' any script using code comments ,and parses the most logical commenting of code,
the point isn't to replace seeing the code for which comments are obviously meant,the point is a brief overview of script functionality and components
at a moments glance such as all functions with their required params,that is the extent and purpose of this 'documentation' generator.
Html files are easily formatted such that,so one can edit it easily, if one wishes to use this for actual documentation,which it works well provided code is logically commented.
Loop line by line buffering comments, clearing buffer if anything but a function/label/Hotkey/Hotstring is encountered,
and attributing buffered comment to found function/label/Hotkey/Hotstring. That simple!
-->Blank lines are ignored and will not clear commentBuffer.
RULES:
Any comment(block,line comment or inline comment) above a function/label/Hotkey/Hotstring will be considered a comment for that below.
Comments are constantly appended to commentBuffer, which is only reset on a function/label/Hotkey/Hotstring OR a line with strings that is none of those.
scriptDescription is parsed from the script header, any comment above all code on the script will be considered part of a script Description.
*/
#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases.
; #Warn ; Enable warnings to assist with detecting common errors.
SendMode Input ; Recommended for new scripts due to its superior speed and reliability.
SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory.
#SingleInstance, Force
#NoTrayIcon
SetBatchLines, -1
showFunction_LabelCallCounts := False
If showFunction_LabelCallCounts
callsPrefix = CALLS:
nativeAhkFunctions := "abs|acos|array|asc|asin|atan|ceil|chr|comobjactive|comobjarray|comobjconnect|comobjcreate|comobject|comobjenwrap|comobjerror|comobjflags|comobjget|comobjmissing|comobjparameter|comobjquery|comobjtype|comobjunwrap|comobjvalue|cos|dllcall|exception|exp|fileexist|fileopen|floor|func|getkeyname|getkeysc|getkeystate|getkeyvk|il_add|il_create|il_destroy|instr|isbyref|isfunc|islabel|isobject|isoptional|ln|log|ltrim|lv_add|lv_delete|lv_deletecol|lv_getcount|lv_getnext|lv_gettext|lv_insert|lv_insertcol|lv_modify|lv_modifycol|lv_setimagelist|mod|numget|numput|objaddref|objclone|object|objgetaddress|objgetcapacity|objhaskey|objinsert|objinsertat|objlength|objmaxindex|objminindex|objnewenum|objpop|objpush|objrawset|objrelease|objremove|objremoveat|objsetcapacity|onmessage|ord|regexmatch|regexreplace|registercallback|round|rtrim|sb_seticon|sb_setparts|sb_settext|sin|sqrt|strget|strlen|strput|strsplit|substr|tan|trim|tv_add|tv_delete|tv_get|tv_getchild|tv_getcount|tv_getnext|tv_getparent|tv_getprev|tv_getselection|tv_gettext|tv_modify|tv_setimagelist|varsetcapacity|winactive|winexist|_addref|_clone|_getaddress|_getcapacity|_haskey|_insert|_maxindex|_minindex|_newenum|_release|_remove|_setcapacity"
unusuallyNamedFunctionsList = ;this variable contains a line delmited list of strings that should they be encountered will be considered functions.
(
)
Loop %0% ; For each parameter (or file dropped onto a script):
{
GivenPath := %A_Index% ; Fetch the contents of the variable whose name is contained in A_Index.
Loop %GivenPath%, 1
LongPath = %A_LoopFileLongPath%
SplitPath, LongPath, , , , ScriptNameNoExt
InputBox, LinkToForums, Input Forums Link, `n%LongPath%`n`n`n Input Link to forums page for %ScriptNameNoExt%`,if none`,just press enter:, , 400, 282, , , , , https://autohotkey.com/boards
If !ErrorLevel
DocGen(LongPath)
}
MsgBox, 0x40040, % RTrim(A_ScriptName, ".ahk"), DONE!
Exit
DocGen(path){ ;parses scripts for function/label/Hotkey/Hotstring ,and corresponding comments.
Global
SplitPath, path, OutFileName, OutDir, OutExtension, OutNameNoExt, OutDrive
scriptTitle := OutNameNoExt ;script name minus extension will be doc title & name.
scriptDescription := "" ;scriptHeader variable rreserved for all comment at the very top of script before any uncommented code.
scriptHeaderEnd := "" ;signifies uncommented code has been encountered hence, can no longer be retrieved if was not so already,as there is no valid header.
commentBuffer := ""
completeScript := "", completeScriptNumberOfLines := ""
blockCommentActive := "", blockCommentWasActiveAtLeastOnce :="", lineCommentWasActiveAtLeastOnce:="", fncList := "", labelList := "", hotkeyList := "", hsList := "", fncIndex:="", labelIndex:="", hotkeyIndex:="", hsIndex :=""
Loop, Read, % path
{
;=========================================================================== Description Parsing Rules Here =======================================================================
;this rule renders top most comment block only as the scriptDescription,i.e in the case that multiple blockcomments are used separated by blank line,only the first will be considered the siteDescription.
/*
blockComment
^^ the above block comment in this case will be the scriptDescription where as the one below will belong to the first function/label/Hotkey/Hotstring that is encountered based on other defined rules.
anotherblockComment after blank line
*/
If ( blockCommentWasActiveAtLeastOnce AND !blockCommentActive AND !scriptHeaderEnd AND commentBuffer AND !StringTrimLeftRightWhiteSpaces(A_LoopReadLine)){
scriptDescription := commentBuffer
scriptHeaderEnd := True
commentBuffer := ""
}
; ^^^similar rule to above, this however evaluates the first consecutive set of line comments followed by a blank line as the scriptDescription
If ( lineCommentWasActiveAtLeastOnce >= 3 AND !blockCommentActive AND !scriptHeaderEnd AND commentBuffer AND !StringTrimLeftRightWhiteSpaces(A_LoopReadLine)){
scriptDescription := commentBuffer
scriptHeaderEnd := True
commentBuffer := ""
}
;skip standard ahk script headers
If ( !scriptHeaderEnd AND AreInStr(A_LoopReadLine, "#NoEnv", "#Warn", "SendMode Input", "SetWorkingDir", "#NoTrayIcon") ){
commentBuffer .= "`n"
Continue ;skip standard ahk header
}
;==================================================================================================================================================================================
;If this line is reached then it's not a comment & is actual code, so should give a more accurate overview of CODE line counts, for what ever that's worth.
completeScript .= StringTrimLeftRightWhiteSpaces(A_LoopReadLine) "`n"
completeScriptNumberOfLines++
;Only ONE Type of comment can be parsed at a time, this ensures consistency
If IsBlockCommentStart(A_LoopReadLine){ ;blockCommentActive activated Inside function
commentBuffer .= LTrim(StringTrimLeftRightWhiteSpaces(A_LoopReadLine), "/*") . "`n"
Continue
}Else If IsBlockCommentEnd(A_LoopReadLine){ ;blockCommentActive deactivated Inside function
commentBuffer .= RTrim(StringTrimLeftRightWhiteSpaces(A_LoopReadLine), "*/") . "`n"
blockCommentWasActiveAtLeastOnce := True
Continue
}Else If blockCommentActive{ ;if inside a blockComment append to commentBuffer
commentBuffer .= StringTrimLeftRightWhiteSpaces(A_LoopReadLine) "`n"
Continue
}Else If IsComment(A_LoopReadLine){
commentBuffer .= StringTrimLeftRightWhiteSpaces(GetCommentThisLine(A_LoopReadLine)) "`n"
Continue
}Else If (ContainsInlineComment(A_LoopReadLine) AND scriptHeaderEnd){
If commentBuffer ;if previous line was a comment or at the very least a blank line increment
lineCommentWasActiveAtLeastOnce++
Else
lineCommentWasActiveAtLeastOnce := 1
commentBuffer .= StringTrimLeftRightWhiteSpaces(GetCommentThisLine(A_LoopReadLine)) "`n"
}Else If !StringTrimLeftRightWhiteSpaces(A_LoopReadLine){ ;blank line
Continue
}Else If !scriptHeaderEnd{ ;if line isn't comment or blank and script header hasn't yet been saved, save it now.
scriptDescription := commentBuffer
scriptHeaderEnd := True
commentBuffer := ""
}
;check for code defined inside a multiline variable & ignore var contents.
If IsMultiLineVarBegin(A_LoopReadLine){ ;IsInMultiLineVar activated inside function
Continue
}Else If IsMultiLineVarEnd(A_LoopReadLine){ ;IsInMultiLineVar ddeactivated inside Function
Continue
}Else If IsInMultiLineVar{
Continue
}
;ignore variable continuity/appending
If StringBeginsWith(StringTrimLeftRightWhiteSpaces(A_LoopReadLine), ".")
Continue
;formatting of parsed function/label/Hotkey/Hotstring as: function/label/Hotkey/Hotstring - nexline blank - comment that belongs to function/label/Hotkey/Hotstring above next.
If StringBeginsWith(A_LoopReadLine, "#"){
Continue
}Else If ( IsFuncDefinition(A_LoopReadLine) OR IsMultiLineFuncDefinition(A_LoopReadLine, path, A_Index) OR ListEntryMatchesString(A_LoopReadLine, unusuallyNamedFunctionsList) ){
fncList .= GetFuncThisLine(A_LoopReadLine) "`n`n" commentBuffer "`n`n"
fncIndex .= GetFuncThisLine(A_LoopReadLine) "`n" ;for crosschecking during html write.
commentBuffer := ""
}Else If IsLabelDefinition(A_LoopReadLine){
labelList .= GetLabelThisLine(A_LoopReadLine) "`n`n" commentBuffer "`n`n"
labelIndex .= GetLabelThisLine(A_LoopReadLine) "`n" ;for crosschecking during html write.
commentBuffer := ""
}Else If IsHotkey(A_LoopReadLine){
hotkeyList .= MakeHumanReadable_Hotkeys(GetHotkeyThisLine(A_LoopReadLine)) "`n`n" commentBuffer "`n`n"
hotkeyIndex .= MakeHumanReadable_Hotkeys(GetHotkeyThisLine(A_LoopReadLine)) "`n" ;for crosschecking during html write.
commentBuffer := ""
}Else If IsHotString(A_LoopReadLine){
hsList .= GetHotStringThisLine(A_LoopReadLine) "`n`n" commentBuffer "`n`n"
hsIndex .= GetHotStringThisLine(A_LoopReadLine) "`n" ;for crosschecking during html write.
commentBuffer := ""
}Else If StringTrimLeftRightWhiteSpaces(A_LoopReadLine){ ;contains strings but not match with anything, signify scriptHeaderEnd
scriptHeaderEnd := True
commentBuffer := ""
}
}
FileDelete, %OutDir%\%scriptTitle%.HTML
FileAppend, % WriteHtml(), %OutDir%\%scriptTitle%.HTML
; MsgBox % scriptDescription
; MsgBox % fncList
; MsgBox % labelList
; MsgBox % hotkeyList
; MsgBox % hsList
; MsgBox % hsIndex
}
GetFuncThisLine(string){ ;returns function definition in string
Return % StringBetweenTwoStrings(StringTrimLeftRightWhiteSpaces(string), "", "`)", "result") ;include delimiters
}
GetLabelThisLine(string){ ;returns label definition
Return % StringBetweenTwoStrings(StringTrimLeftRightWhiteSpaces(string), "", "`:", "result") ;include delimiters
}
GetHotkeyThisLine(string){ ;returns line if uncommented else return left side of comment
return % ( InStr(string, "`;") ? StringBetweenTwoStrings(StringTrimLeftRightWhiteSpaces(string), "", "`;") : StringTrimLeftRightWhiteSpaces(string))
}
GetHotStringThisLine(string){ ;returns line if uncommented else return left side of comment
return % ( InStr(string, "`;") ? StringBetweenTwoStrings(StringTrimLeftRightWhiteSpaces(string), "", "`;") : string )
}
GetCommentThisLine(string){ ;returns right side of comment delimiter
return % StringBetweenTwoStrings(StringTrimLeftRightWhiteSpaces(string), "`;", "")
}
MakeHumanReadable_Hotkeys(HK){ ;Code Snippet from fanatic guru's 'Hotkey Help' - Basically makes something like '^a' look like 'Ctrl+a'
Set_Hotkey_Mod_Delimiter := "+" ; Delimiter Character to Display Between Hotkey Modifiers
StringReplace, HK, HK, +, Shift%Set_Hotkey_Mod_Delimiter%
StringReplace, HK, HK, <^>!, AltGr%Set_Hotkey_Mod_Delimiter%
StringReplace, HK, HK, <, Left, All
StringReplace, HK, HK, >, Right, All
StringReplace, HK, HK, !, Alt%Set_Hotkey_Mod_Delimiter%
StringReplace, HK, HK, ^, Ctrl%Set_Hotkey_Mod_Delimiter%
StringReplace, HK, HK, #, Win%Set_Hotkey_Mod_Delimiter%
if Set_CapHotkey
if (Set_CapHotkey_Radio = 1)
HK := RegExReplace(HK,"((^[^\Q" Set_Hotkey_Mod_Delimiter "\E]*|\Q" Set_Hotkey_Mod_Delimiter "\E[^\Q" Set_Hotkey_Mod_Delimiter "\E]*))","$T1")
else
HK := RegExReplace(HK,"((^[^\Q" Set_Hotkey_Mod_Delimiter "\E]*|\Q" Set_Hotkey_Mod_Delimiter "\E[^\Q" Set_Hotkey_Mod_Delimiter "\E]*))","$U1")
Return HK
}
;
IsFuncDefinition(string){ ;Function - Using StringCharCount as JEE_RegExMatchAll seems to return nothing for "(",")"
StringSplit, bracketSplitArray, string, `( ;to avoid designating built in calls like 'If (x = y){' or 'If InStr(haystack, needle){' as function definitions.
If !ContainsInlineComment(string)
Return % ( StringCharCount(string, "`(") = 1 && StringCharCount(string, "`)") = 1 && StringCharCount(string, "`{") = 1 && StringCharCount(string, "`}") = 0 && !ListEntryMatchesString(StringBetweenTwoStrings(StringTrimLeftRightWhiteSpaces(string), "", "`("), nativeAhkFunctions, "|") && !AreInStr(StringTrimLeftRightWhiteSpaces(bracketSplitArray1), A_Space, "`%") && !IsOneLikeAnyOther(StringTrimLeftRightWhiteSpaces(bracketSplitArray1), "if", "while") ? True : False ) ;Function
Else
Return % ( StringCharCount(StringBetweenTwoStrings(string,"","`;"), "`(") = 1 && StringCharCount(StringBetweenTwoStrings(string,"","`;"), "`)") = 1 && StringCharCount(StringBetweenTwoStrings(string,"","`;"), "`{") = 1 && StringCharCount(StringBetweenTwoStrings(string,"","`;"), "`}") = 0 && !ListEntryMatchesString(StringBetweenTwoStrings(StringTrimLeftRightWhiteSpaces(string), "", "`("), nativeAhkFunctions, "|") && !AreInStr(StringTrimLeftRightWhiteSpaces(bracketSplitArray1), A_Space, "`%") && !IsOneLikeAnyOther(StringTrimLeftRightWhiteSpaces(bracketSplitArray1), "if", "while") ? True : False ) ;Function
}
IsMultiLineFuncDefinition(string, scriptPath, currentLineNum){
StringSplit, bracketSplitArray, string, `( ;to avoid designating built in calls like 'If (x = y){' or 'If InStr(haystack, needle){' as function definitions.
FileReadLine, nextLine, %scriptPath%, % currentLineNum + 1
If !ContainsInlineComment(string)
Return % ( StringCharCount(string, "`(") = 1 && StringCharCount(string, "`)") = 1 && StringBeginsWith(StringTrimLeftRightWhiteSpaces(nextLine), "`{") && StringCharCount(string, "`}") = 0 && !ListEntryMatchesString(StringBetweenTwoStrings(StringTrimLeftRightWhiteSpaces(string), "", "`("), nativeAhkFunctions, "|") && !AreInStr(StringTrimLeftRightWhiteSpaces(bracketSplitArray1), A_Space, "`%") && !IsOneLikeAnyOther(StringTrimLeftRightWhiteSpaces(bracketSplitArray1), "if", "while") ? True : False ) ;Function
Else
Return % ( StringCharCount(StringBetweenTwoStrings(string,"","`;"), "`(") = 1 && StringCharCount(StringBetweenTwoStrings(string,"","`;"), "`)") = 1 && StringBeginsWith(StringTrimLeftRightWhiteSpaces(nextLine), "`{") && StringCharCount(StringBetweenTwoStrings(string,"","`;"), "`}") = 0 && !ListEntryMatchesString(StringBetweenTwoStrings(StringTrimLeftRightWhiteSpaces(string), "", "`("), nativeAhkFunctions, "|") && !AreInStr(StringTrimLeftRightWhiteSpaces(bracketSplitArray1), A_Space, "`%") && !IsOneLikeAnyOther(StringTrimLeftRightWhiteSpaces(bracketSplitArray1), "if", "while") ? True : False ) ;Function
}
IsLabelDefinition(string){
Return % ( JEE_RegExMatchAll(string, ":") = 1 && JEE_RegExMatchAll(string, "`:=") = 0 && JEE_RegExMatchAll(string, "``:") <> 1 && !AreInStr(StringBetweenTwoStrings(StringTrimLeftWhiteSpaces(string), "", "`:"), "`,", A_Space) && StringBetweenTwoStrings(StringTrimLeftRightWhiteSpaces(string), "", "`:") && !InStr(string, ":=") ? ( !StringBetweenTwoStrings(StringTrimLeftRightWhiteSpaces(string), "`:", "") || StringBeginsWith(StringBetweenTwoStrings(StringTrimLeftRightWhiteSpaces(string), "`:", ""), "`;") ? True : False ) : False )
}
IsHotkey(string){
Return % ( JEE_RegExMatchAll(string, "::") = 1 && JEE_RegExMatchAll(string, ":") = 2 && !AreInStr(StringTrimLeftRightWhiteSpaces(StringBetweenTwoStrings(string, "", "::")),"RegEx","`(", "`)") ? ( JEE_RegExMatchAll(string, "``:") = 0 ? True : False ): False ) ;Hotkey
}
IsHotString(string){
Return % ( JEE_RegExMatchAll(string, "::") >= 1 && StringBeginsWith(StringTrimLeftWhiteSpaces(string), ":") && JEE_RegExMatchAll(string, ":") = 4 ? ( JEE_RegExMatchAll(string, "``:") = 0 ? True : False ): False ) ;Hotstring
}
IsMultiLineVarBegin(string){ ;returns true on encountering a multiline variable start,and acitvates multiline tracking variable.
Global
If !ContainsInlineComment(string)
Return % ( StringBeginsWith(StringTrimLeftRightWhiteSpaces(string), "`(") && StringCharCount(string, "`(") = 1 && StringCharCount(string, "`)") = 0 ? IsInMultiLineVar := True : False )
Else
Return % ( StringBeginsWith(StringTrimLeftRightWhiteSpaces(string), "`(") && StringCharCount(StringBetweenTwoStrings(string, "", "`;"), "`(") = 1 && StringCharCount(StringBetweenTwoStrings(string, "", "`;"), "`)") = 0 ? IsInMultiLineVar := True : False )
}
IsMultiLineVarEnd(string){ ;returns true on encountering a multiline variable's end & sets multiline tracking var to false.
Global
If (StringBeginsWith(StringTrimLeftRightWhiteSpaces(string), "`)") AND IsInMultiLineVar){ ;can't imagine a scenario where a non-comment line would start with ')',other than this.
IsInMultiLineVar := False
Return True
}
}
;======================================= Comment Parsing Helpers =======================================
ContainsInlineComment(string){ ;returns true if line contains an inline Comments
Return % ( JEE_RegExMatchAll(string, A_Space . "`;") = 1 || JEE_RegExMatchAll(string, A_Tab . "`;") = 1 ? ( JEE_RegExMatchAll(string, "`;") = 1 ? True : False ) : False ) ;comment
}
IsComment(string){ ;returns true if current line is a Comment
If StringBeginsWith(StringTrimLeftWhiteSpaces(string), "`;")
Return True
}
IsBlockCommentStart(string){ ;returns true & activates Global variable blockCommentActive, when a block Comment start is detected
Global
If StringBeginsWith(StringTrimLeftWhiteSpaces(string), "/*")
Return, % blockCommentActive := True ;signifies current line is part of a block comment,until deactivated by IsBlockCommentEnd
}
IsBlockCommentEnd(string){ ;deactivates blockCommentActive tracker to signify current succeding lines are no longer part of block comment
Global
If (StringBeginsWith(StringTrimLeftWhiteSpaces(string), "*/") AND blockCommentActive){
blockCommentActive := False ;signifies current line is part of a block comment,until deactivated by IsBlockCommentEnd
Return True
}
}
;======================================= String Parsing Helpers =======================================
StringBeginsWith(string, char){ ;returns true if passed variable begins with specified char
Return % (SubStr(string, 1, StrLen(char)) = char ? True : False)
}
StringEndsWith(string, char){ ;returns true if passed variable ends with specified char
Return % (SubStr(string, StrLen(string)-StrLen(char)+1, StrLen(string)) = char ? True : False)
}
StringTrimLeftWhiteSpaces(string){
Return % regexreplace(string, "^\s+") ;trim beginning whitespace
}
StringTrimRightWhiteSpaces(string){
Return % regexreplace(string, "\s+$") ;trim ending whitespace
}
StringTrimLeftRightWhiteSpaces(string){ ;trim whitespaces on either side
Return % regexReplace(string, "^\s+|\s+$")
}
;MsgBox % StringBetweenTwoStrings("#Hello World.", "#", "") ;returns 'Hello world.'
;MsgBox % StringBetweenTwoStrings("#Hello World.", "#", "", "result") ;returns '#Hello World.'
;MsgBox % StringBetweenTwoStrings("#Hello World.", "", "d") ;returns '#Hello worl'
;MsgBox % StringBetweenTwoStrings("#Hello World.", "#", "d") ;returns 'Hello worl'
;MsgBox % StringBetweenTwoStrings("#Hello World.", "#", "d", "result") ;returns '#Hello World'
/*
returns string between two delimiters,
'result1' *default outputs strings between start-end, excluding the start-end chars.
'result' outputs strings between start-end, including the start-end chars.
*/
StringBetweenTwoStrings(string, startsWith, endsWith, returnWhat:="result1"){ ;returns string between two delimiters, if returnWhat = "result",the delimiters will be Included in the result.
StringSplit, sideSplit, string, %startsWith%
foundAtPos := RegExMatch(string, "s)\Q" . startsWith . "\E(.*?)\Q" . endsWith . "\E", result)
Return % ( returnWhat = "result1" ? ( !endsWith && startsWith ? sideSplit2 : result1) : ( returnWhat = "result" && !endsWith && startsWith ? startsWith . sideSplit2 : result ) )
}
;MsgBox, % StringSplit("Hello World", " ",,1) ;Returns Hello
;MsgBox, % StringSplit("Hello World", " ",,2) ;Returns World
StringSplit(string, delim="", omitChars:="", stringArray2Return:=0){ ;by default returns number of split elements
StringSplit, stringArray, string, %delim%, %omitChars%
Return % stringArray%stringArray2Return%
}
StringCharCount(string, char){ ;Returns the number of ooccurrences of a character in a string
StringReplace, string, string, %char%, %char%, UseErrorLevel
Return ErrorLevel
}
;returns true(the number of matches) if any char exists in the string, NOTE: Only Single characters are valid params.
IsCharInString(string, chars*){
Loop, Parse, string ;character by character
{
for index,char in chars ;do any of the chars exist in the string.
IfEqual, A_LoopField, % char
i++
}
Return i
}
AreInStr(haystack, needles*){ ;returns the number of needles that matched
for index,needle in needles ;do any of the needles exist in the haystack.
IfInString, haystack, % needle
i++
Return i
}
IsOneLikeAnyOther(varIn, possibleValue*){ ;returns true(the number of matches) if any possibleValue matches varIn
for index,param in possibleValue
IfEqual, varIn, % param
i++
return i
}
ListEntryMatchesString(string, list, delim:="`n"){ ;Returns true if an entry in a delimter(*default line) delimited list matches string.
Loop, Parse, list, %delim%
IfEqual, A_LoopField, %string%
Return True
}
/*
SuperInstr()
Returns min/max position for a | separated values of Needle(s)
return_min = true ; return minimum position
return_min = false ; return maximum position
*/
SuperInstr(Hay, Needles, return_min=true, Case=false, Startpoint=1, Occurrence=1){
pos := return_min*Strlen(Hay)
if return_min
{
loop, parse, Needles,|
if ( pos > (var := Instr(Hay, A_LoopField, Case, startpoint, Occurrence)) )
pos := var ? var : pos
if ( pos == Strlen(Hay) )
return 0
}
else
{
loop, parse, Needles,|
if ( (var := Instr(Hay, A_LoopField, Case, startpoint, Occurrence)) > pos )
pos := var
}
return pos
}
;vText := "aabcdeaafghijklmnopqrstuvwxyz"
;vNeedle := "aa"
;vCount := JEE_RegExMatchAll(vText, vNeedle, oMatch)
;MsgBox, % vCount "`r`n" JEE_StrJoin(",", oMatch*)
;================================================== jeeswg's functions ==================================================
JEE_RegExMatchAll(vText, vNeedle, ByRef oMatch:="", vPos:=1)
{
;we're not interested in the return value, but we need it to get RegExMatch to take place
static vRet := RegExMatch("", "", o), vIsV1 := !IsObject(o)
if vIsV1
{
if (vPos = 0)
return
else if (vPos <= -1)
vPos++
if RegExMatch(vNeedle, "^[A-Za-z`a`n`r `t]*\)")
vNeedle := "O" vNeedle
else
vNeedle := "O)" vNeedle
}
vCount := 0, oMatch := []
while vPos := RegExMatch(vText, vNeedle, oTemp, vPos)
{
vCount += 1, vPos += StrLen(oTemp.0)
if (vPos = vPosLast) ;prevent an infinite loop
return -1
oMatch.Push(oTemp.0), vPosLast := vPos
}
return vCount
}
;==================================================
; ;e.g.
; oArray := StrSplit("abcdefghijklmnopqrstuvwxyz")
; MsgBox, % JEE_StrJoin(" - ", oArray*)
; MsgBox, % JEE_StrJoin(["=","`r`n"], oArray*)
; MsgBox, % JEE_StrJoin(["`t","`r`n"], oArray*)
; MsgBox, % JEE_StrJoin(["`t","`t","`r`n"], oArray*)
; MsgBox, % JEE_StrJoin(["`t","`t","`t","`r`n"], oArray*)
; MsgBox, % JEE_StrJoin(["`t","`t","`t","`t","`r`n"], oArray*)
; MsgBox, % JEE_StrJoin(["","","","","`r`n"], oArray*)
JEE_StrJoin(vSep, oArray*)
{
VarSetCapacity(vOutput, oArray.Length()*200*2)
if (vSep.Length() = 1) ;convert 1-item array to string
vSep := vSep.1
if !IsObject(vSep)
{
Loop, % oArray.MaxIndex()-1
vOutput .= oArray[A_Index] vSep
vOutput .= oArray[oArray.MaxIndex()]
}
else
{
oSep := vSep, vCount := oSep.Length(), vIndex := 0
Loop, % oArray.MaxIndex()-1
{
;vIndex := Mod(A_Index-1, vCount)+1
vIndex := (vIndex = vCount) ? 1 : vIndex+1
, vOutput .= oArray[A_Index] oSep[vIndex]
}
vOutput .= oArray[oArray.MaxIndex()]
}
return vOutput
}
;==================================================
;Outputs formatted HTML using list of function/label/Hotkey/Hotstring 's collected by DocGen()
WriteHtml(){
Global
Local html_header,html_Description,html_hotkeys,html_hotstrings,html_functions,html_labels,html_footer
hotkeysNav = <p><a href="#HotKeys">HotKeys</a></p
hotstringsNav = <p><a href="#HotStrings">HotStrings</a></p
functionsNav = <p><a href="#Functions">Functions</a></p
labelsNav = <p><a href="#Labels">Labels</a></p>
;nav elements only appear if at least one entry exists for a corresponding element
hotkeysNav := ( hotkeyList ? hotkeysNav : "" )
hotstringsNav := ( hsList ? hotstringsNav : "" )
functionsNav := ( fncList ? functionsNav : "" )
labelsNav := ( labelList ? labelsNav : "" )
;Title & Headers
html_header=
(
<!DOCTYPE html><html class="hcfe" lang="en"><head>
<style>
samp {
font-family: monospace;
}
</style>
<title>%scriptTitle% - [DocGen]</title>
<style data-id="all">html{-webkit-text-size-adjust:100`%}html.hcfe{background-color:#f5f5f5;overflow-y:scroll}body.mobile{margin:0;position:relative}div{outline:none}.hcfe{background-color:#fff;color:#212121;font-family:Roboto, "Helvetica Neue", Helvetica, sans-serif;font-size:14px;line-height:20px}.hcfe p{margin:5px 0}.hcfe a{color:#4285f4;text-decoration:none}.hcfe a img{border:0}.hcfe article section section{padding:0}.hcfe ol,.hcfe ul{margin:0;outline:0;padding:0;vertical-align:baseline}.hcfe h1,.hcfe h2,.hcfe h3,.hcfe h4,.hcfe h5{color:#212121;font-weight:400}.hcfe h1{font-size:24px;line-height:32px;margin:0 0 8px}.hcfe h2{font-size:20px;line-height:32px;margin:32px 0 8px}.hcfe h3{font-size:18px;font-weight:500;line-height:28px;margin:24px 0 8px}.hcfe h4{font-size:16px;font-weight:700;line-height:24px;margin:20px 0 4px}.hcfe h5{font-size:14px;font-weight:inherit;line-height:24px;margin:16px 0 4px}.hcfe button,.hcfe input,.hcfe select,.hcfe textarea{color:#212121;font-family:Roboto, "Helvetica Neue", Helvetica, sans-serif;font-size:14px;line-height:20px}.hcfe select{background:none;border:solid #d9d9d9;border-width:0 0 1px 0;font-size:13px;font-weight:bold;max-width:100`%;outline:0;padding:5px}.hcfe img{vertical-align:middle}.hcfe article section{padding:16px}.hcfe>header{background-color:#4285f4}.searchbar{background-color:#4285f4;color:#fff;overflow:hidden;padding:0 65px 0 16px;text-overflow:ellipsis;white-space:nowrap}.searchbar ::-webkit-input-placeholder{color:#757575}.searchbar ::-moz-placeholder{color:#757575}.searchbar svg{fill:#fff;height:24px;width:24px}.hcfe .search-title{color:#fff;font-size:20px;line-height:56px}.toggles{position:absolute;right:16px;top:12px}.toggles>.more-toggle,.toggles>.search-toggle{background:none;border:0;padding:4px 3px 0;position:relative;z-index:1}.toggles>.more-toggle{margin-left:8px}button.search-toggle:focus,button.more-toggle:focus{outline:dotted thin #fff}.search-toggle .close-icon,.search-toggle.active .search-icon{display:none}.search-toggle.active .close-icon{display:inline-block}.search-toggle.active svg{fill:#757575}.search-toggle.active+.more-toggle{display:none}.search{display:none}.searchbar .search-back{left:16px;position:absolute;top:16px}.rtl .searchbar .search-back svg{transform:scaleX(-1)}form.search .search-box{background-color:#f5f5f5;border:none;border-radius:0;color:#212121;font-size:18px;height:56px;outline:0;padding:0 40px 0 72px;width:100`%;-webkit-appearance:none}.show-search .search{display:inline}.search-container.show-search{background-color:#3367d6;left:0;position:absolute;top:0;width:100`%}.search-overlay{display:none}.search-overlay.active{background-color:#212121;bottom:0;display:block;opacity:.5;position:absolute;top:56px;width:100`%;z-index:1000}input[type=text]::-ms-clear{display:none;width:0;height:0}input[type=text]::-ms-reveal{display:none;width:0;height:0}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-results-button,input[type="search"]::-webkit-search-results-decoration{display:none}.appbar-link-container{display:inline-block;margin-right:4px}.appbar-link-container>a{color:#fff;padding:0 16px;line-height:48px;text-transform:uppercase}.appbar-link-container>a svg{fill:#fff;height:24px;margin-bottom:2px;margin-left:9.5px;vertical-align:middle;width:24px}.appbar{background-color:#4285f4}.cc{word-wrap:break-word}.cc h1{margin:24px 0 8px}.cc>h2>a,.cc>h3>a,.cc>h4>a{color:#212121}.cc>h2>a:hover,.cc>h3>a:hover,.cc>h4>a:hover{text-decoration:none}.cc div,.cc p,.cc ol,.cc ul{margin:4px 0 12px}.cc ul li{margin:4px 0 4px 26px}.cc ol li{margin:4px 0 4px 20px}.cc ol ol{list-style-type:lower-latin}.cc ul ul,.cc ol ul{list-style-type:disc}.cc code{color:#060}.cc pre{direction:ltr;margin:0;text-align:left;white-space:pre-wrap;word-wrap:break-word}.cc a:visited{color:#7759ae}.cc a[target="_blank"]:after,.notification a[target="_blank"]:after{content:'';background:no-repeat url(https://ssl.gstatic.com/support/content/images/sprites/core-mobile-52bf8ba6d40a366fb44d1a5ebcd44e8c.png) 0 0;display:inline-block;height:10px;margin:0 3px 0 5px;width:10px}.rtl .cc a[target="_blank"]:after,.rtl .notification a[target="_blank"]:after{transform:scaleX(-1)}.cc iframe{max-width:100`%}.cc ul>li>ul{margin:0}.cc ul:not(.no-bullets):not(.no_bullets)>li{list-style-type:none;margin-left:0;padding-left:16px}.cc ul:not(.no-bullets):not(.no_bullets)>li:before{content:"\002022";float:left;font-size:16px;margin-left:-14px}.cc blockquote{margin:0 0 0 30px}.cc hr{border:none;border-top:1px dashed #e5e5e5}.img-2g{display:inline-block;height:auto !important}.hcfe img.float-left{float:left;margin-right:1em;max-width:50`%}.hcfe img.float-right{float:right;margin-left:1em;max-width:50`%}.hcfe img.screenshot{height:auto !important;width:100`%}.cc img{max-width:100`%}.cc table{border-collapse:collapse;border-spacing:0;margin:0;padding:0;border:0;outline:0}.cc .table-basic,.cc .table-stacked{margin-top:16px}.cc .table-basic caption,.cc .table-stacked caption{color:#212121;font-size:20px;font-weight:400;line-height:24px;padding-bottom:16px;text-align:left}@media all and (max-width:770px){.cc .table-basic caption,.cc .table-stacked caption{color:#757575}}.cc td{vertical-align:top}.cc td,.cc th{padding:13px 5px;position:relative}.cc th{color:#757575;font-weight:bold;text-align:left;vertical-align:baseline}.cc table .align-middle{vertical-align:middle}.page{display:inline-block;position:relative;width:100`%}.page{box-shadow:0 2px 5px 0 rgba(0,0,0,.26)}.sub-article-container.shaded{border-top:1px solid #e0e0e0;background-color:#fafafa}.sub-article-container{clear:both;margin-top:-16px}.as{padding:31px 10px 46px 10px}.as.rated{padding-bottom:40px}.as.submitted{padding-bottom:16px}.as .title{color:#757575;font-weight:bold}.as .subtitle{color:#757575;margin-bottom:10px}.as:not(.rated) form,.as.rated .as-button,.as.submitted form{display:none}.as .as-button{background-color:#fafafa;border:0;border-radius:3px;box-shadow:0 2px 5px 0 rgba(0,0,0,.26);display:inline-block;line-height:16px;margin:6px 8px 6px 0;min-width:88px;padding:10px 0;text-align:center;text-transform:uppercase}.as .as-button:active{background-color:#e0e0e0}.as .as-button:hover{color:#757575}.as form{padding-top:0}.as .field{margin:16px 0}.as .field:first-of-type{margin-top:4px}.as textarea{border:1px solid #e0e0e0;box-sizing:border-box;color:#222222;max-width:448px;outline:none;padding:16px;resize:none;width:100`%}.as .submit-button{background:#4285f4;border:0;border-radius:3px;box-shadow:0 2px 5px 0 rgba(0,0,0,.26);color:#fff;font-size:14px;line-height:16px;margin-top:8px;outline:0;padding:10px 16px;text-transform:uppercase}.as .submit-button:active{background:#3367d6;box-shadow:0 4px 8px 0 rgba(0,0,0,.4)}.social-sharing-widgets{border-top:0}.primary-nav{background-color:#4285f4}.primary-nav nav{margin-left:10px;width:256px}.sibling-nav{display:inline-block;margin-top:10px}.sibling-nav h4{margin-bottom:16px}.sibling-nav>ul{list-style:none}.sibling-nav a.title-link,.sibling-nav a.title-link:visited{color:#fff}.sibling-nav .sibling-list{margin-bottom:15px}.sibling-nav .sibling-list li{padding:0}.sibling-nav .sibling-list a.sibling-link.sibling-link--current{border-left:2px solid #fff;color:#fff;font-weight:700}.sibling-nav .sibling-list a.sibling-link{border-left:2px solid transparent;color:#e3edfe;display:block;padding:14px 0 14px 14px}.sibling-nav .sibling-list a.sibling-link.sibling-link--current.sibling-link--has-children{border-left-color:transparent}.sibling-nav .sibling-link--has-children:before{content:'▶';margin-left:-16px;position:absolute;transform:scale(.3,.6)}.sibling-nav .sibling-link--has-children.sibling-link--show-children:before{content:'▼';margin-left:-18px;transform:scale(.6,.3)}.sibling-nav .sibling-link--has-children+.sibling-sublist{display:none}.sibling-nav .sibling-link--has-children.sibling-link--show-children+.sibling-sublist{display:block}.sibling-nav .sibling-sublist{list-style:none;padding-left:20px}footer{background-color:#f5f5f5;padding:16px 0}footer ul{display:inline-block;list-style:none;margin:0;outline:0;padding:0;vertical-align:baseline}footer li{display:inline-block}footer li+li:before{content:'-'}footer>div{margin:10px 20px 20px 20px}.slow_con_container{text-align:center}.footer-links{color:#999;margin:0 10px}.footer-links a{color:#999;text-decoration:none}.footer-links ul{font-size:13px;line-height:48px;width:initial}.language-selector{display:inline-block}.language-selector select{font-size:11px;padding:6px 3px}.csi{display:none}</style><link></head><body class="mobile"> <div class="hcfe"> <header><div class="searchbar" st-ve="10">
<a class="search-title" href="https://autohotkey.com/">Download AutoHotKey</a>
<div st-ve="32"><div class="appbar"><div class="appbar-links"><span class="appbar-icon appbar-link-container">
<a href="%LinkToForums%"st-ve="36" target="_blank"><span itemprop="title">Script Forums Link</span><svg viewbox="0 0 48 48"><path d="M20.17 31.17L23 34l10-10-10-10-2.83 2.83L25.34 22H6v4h19.34l-5.17 5.17zM38 6H10c-2.21 0-4 1.79-4 4v8h4v-8h28v28H10v-8H6v8c0 2.21 1.79 4 4 4h28c2.21 0 4-1.79 4-4V10c0-2.21-1.79-4-4-4z"></path></svg></a></span>
</div></div></div></header><div class="search-overlay search-toggle"></div> <section class="primary-container"> <article class="page"> <section data-tracking-cat="article-container" st-ve="35">
<!-- Navigation Links At Top -->
<a href="#Description">Description</a>
%hotkeysNav%
%hotstringsNav%
%functionsNav%
%labelsNav%
<!-- ScriptName -->
<h1 id="Description">%scriptTitle%</h1><div class="cc">
)
;script description
Loop, Parse, scriptDescription, `n
html_Description .= "<p>" A_LoopField "</p>`n"
;script Line count
lineCount := "<p><center><strong><q>" completeScriptNumberOfLines " LINES </q></strong></center></p>"
;HotKeys
If hotkeyList
html_hotkeys =
(
<h1 id="HotKeys">Hotkeys</h1>
<ul>
)
Loop, parse, hotkeyList, `n
{
If A_LoopField{
If InStr(hotkeyIndex, A_LoopField)
html_hotkeys .= "<li>" A_LoopField "</li> `n <samp> </samp> `n"
Else
html_hotkeys .= "<samp>" StrReplace(A_LoopField, "`n", "<br/>") "</samp><br/> `n"
}
}
html_hotkeys .= "`n</ul>`n"
;HotStrings
If hsList
html_hotstrings =
(
<h1 id="HotStrings">HotStrings</h1>
<ul>
)
Loop, parse, hsList, `n
{
If A_LoopField{
If IsHotString(A_LoopField){
html_hotstrings .= "<li>" StringSplit(A_LoopField, "::",, 3) "</li> `n <p><i></i> <em><b> </b></em></p></li> `n" ;3 Returns left side in this case
html_hotstrings .= "<p><i>Expands to:</i> <em><b>" StringSplit(A_LoopField, "::",, 5) "</b></em></p></li> `n <samp> </samp> `n" ;5 Returns right side in this case
}Else
html_hotstrings .= "<samp>" StrReplace(A_LoopField, "`n", "<br/>") "</samp><br/> `n"
}
}
html_hotstrings .= "`n</ul>`n"
;Functions
If fncList
html_functions =
(
<h1 id="Functions">Functions</h1>
<ul>
)
Loop, parse, fncList, `n
{
If A_LoopField{
If showFunction_LabelCallCounts
thisFncCallCount := StringCharCount(completeScript, StringBetweenTwoStrings(A_LoopField, "", "`(", "result")) - 1 ;minus the function definition
If InStr(fncIndex, A_LoopField)
html_functions .= "<code>" A_LoopField "</code><span style=" """padding-left:4em""" ">" callsPrefix thisFncCallCount "</span> `n <blockquote>" "</blockquote> `n"
Else
html_functions .= "<blockquote>" StrReplace(A_LoopField, "`n", "<br/>") "</blockquote><br/> `n"
}
}
html_functions .= "`n</ul>`n"
;Labels
If labelList
html_labels =
(
<h1 id="Labels">Labels</h1>
<ul>
)
Loop, parse, labelList, `n
{
If showFunction_LabelCallCounts
thisLabelCallCount := StringCharCount(completeScript, StringBetweenTwoStrings(A_LoopField, "", "`:")) - 1 ;minus the label definition
If A_LoopField{
If InStr(labelIndex, A_LoopField)
html_labels .= "<code>" A_LoopField "</code><span style=" """padding-left:4em""" ">" callsPrefix thisLabelCallCount "</span> `n <blockquote>" "</blockquote> `n"
Else
html_labels .= "<blockquote>" StrReplace(A_LoopField, "`n", "<br/>") "</blockquote><br/> `n"
}
}
html_labels .= "`n</ul>`n"
;Footer
html_footer=
(
<p>
</p>
<p>
</p>
<p>
</p>
<!-- Navigation Links At Bottom -->
<a href="#Description">Description</a>
%hotkeysNav%
%hotstringsNav%
%functionsNav%
%labelsNav%
</section><footer><div class="slow_con_container" data-tracking-cat="footer-standard" st-ve="18">@brutus_skywalker<br> <a href="https://autohotkey.com/boards/viewtopic.php?f=6&t=50304">DocGen Forums Link</a></div>
)
Return % html_header . html_Description . lineCount . html_hotkeys . html_hotstrings . html_functions . html_labels . html_footer
}