WBStudio.AddUdfSyntaxToMenu
;==========================================================================================================================================
; WinBatch Studio Tool
; AddUdfSyntaxToMenu
;------------------------------------------------------------------------------------------------------------------------------------------
; This WinBatch WBStudio Tool "AddUdfSyntaxToMenu" (runtime filename: "wsp-udf.wbt") embeds menu entries
; for user defined UDFs into the "wspopup.mnu" file under the menu topic "Insert WIL function...".
;------------------------------------------------------------------------------------------------------------------------------------------
; 20110203: Version 3.00 ; Changed to simple line format in "wsp-udf.mnu" and other enhancements by Detlev Dalitz.
; 20110201: Version 2.00 ; Recoded and enhanced version by Detlev Dalitz.
; 20101211: Version 1.00 ; Initial idea and first code version by Lars M. Doornbos, published in WinBatch forum.
;
; (c)Detlev Dalitz.20110201.20110203.
;==========================================================================================================================================
;
; Installation
;
; - Run this script file. It will create a copy of itself using the name "wsp-udf.wbt" into the
;   folder "DirHome() : 'wsp-udf\'" (typically "C:\Program Files\WinBatch\System\wsp-udf\").
;   The new "wsp-udf.wbt" file from within the new folder will be called from the popup menu further on.
;
; - Integration into "wsp-user.mnu", insert this menu entry:
;   -------------------------------------------------------------------
;   _Embed UDF Syntax Template Into WBS &Menu
;       If wGetSelState ()
;          wGotoLine (ItemExtract (1, wSelInfo (), @TAB))
;          wClearSel ()
;       EndIf
;       wCopyLine()
;       Call (DirHome() : "wsp-udf\wsp-udf.wbt", '"' : ClipGet() : '"')
;       Drop (intLine)
;       Return
;   -------------------------------------------------------------------
;
;------------------------------------------------------------------------------------------------------------------------------------------
;
; Usage in WinBatch Studio
;
; - Place the cursor on the script line containing the text "#DefineFunction" resp. "#DefineSubRoutine".
; - Open the context menu (MouseRightClick or [Shift+F10]) and choose "Add UDF Syntax Template To WBS &Menu" from the menu.
;   Then the embedding process, using the given text line, will start and "wspopup.mnu" will be updated.
;
;------------------------------------------------------------------------------------------------------------------------------------------
;
; Additional informations
;
; When needed, this tool creates a backup file in the folder DirHome() : "Backup\" for the files:
; - wspopup.mnu
; - wsp-udf.mnu
;
; The "wsp-udf.mnu" file is not compatible with the WIL "wspopup.mnu" file.
; The "wsp-udf.mnu" file holds lines with each line containing the menu title, the menu item and the comment.
; During the embedding process into the "wspopup.mnu" file, each line will be transformed into the correct WIL menu entry.
;
; Each line in the "wsp-udf.mnu" file holds an item list of three items delimited by the caret character "^".
; Each line has the structure: "<menu_title>^<udf_with_paramlist>^<comment>".
; The menu items in the "wsp-udf.mnu" can be manually edited.
;
; Example:
;
;    The UDF menu file "wsp-udf.mnu" ...
;
;    |<menu_title>^<udf_with_paramlist>^<comment>
;    ------------------------------------------------------------------
;    |My Functions ABC^A_Function (Param1, Param2)^Any Comment.
;    |My Functions ABC^B_Function (Param1)^Any Comment.
;    |My Functions DEF^F_Function (Param1, Param2, Param3)^Any Comment.
;    ------------------------------------------------------------------
;
;    ... will be transformed to "wspopup.mnu" ...
;    --------------------------------------------------------------------------------------
;    |Insert WIL Function....                ; Insert function template into document.
;    |;[>>> Begin === User Defined Menu Entries ===]
;    | User Defined....
;    |  My Functions ABC....
;    |   A_Function ;^A_Function^(Param1, Param2)^Any Comment.
;    |    Call(zxc,"A_Function")
;    |   B_Function ;^B_Function^(Param1)^Any Comment.
;    |    Call(zxc,"B_Function")
;    |  My Functions DEF....
;    |   F_Function ;^F_Function^(Param1, Param2, Param3)^Any Comment.
;    |    Call(zxc,"F_Function")
;    |;[<<< End === User Defined Menu Entries ===]
;    --------------------------------------------------------------------------------------
;
;==========================================================================================================================================

;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction udfStrSplitAtStr (strString, strSplit) ; Split a string into three parts by another string.
arrResult = ArrDimension (3)
ArrInitialize (arrResult, "")
arrResult[0] = strString
arrResult[1] = strSplit
If !StrIndex (strString, strSplit, 1, @FWDSCAN) Then Return arrResult
intPosMax = StrScan (strString, """'`", 1, @FWDSCAN)
If intPosMax
   arrQ = Arrayize ("""*"",'*',`*`", ",")
   For intQ = 0 To 2
      While @TRUE
         intPos = StrIndexWild (strString, arrQ[intQ], intPosMax)
         If intPos == 0 Then Break
         intLen = StrLenWild (strString, arrQ[intQ], intPos) ; strWild = StrSubWild (strString, arrQ[intQ], intPos)
         intPosMax = intPos + intLen
      EndWhile
   Next
EndIf
intPos = StrIndex (strString, strSplit, intPosMax, @FWDSCAN)
If intPos
   arrResult[0] = StrSub (strString, 1, intPos - 1)  ; Leave as is, no trimming.
   arrResult[2] = StrSub (strString, intPos + StrLen (strSplit), -1) ; Leave as is, no trimming.
EndIf
Return arrResult
;..........................................................................................................................................
; This UDF udfStrSplitAtStr splits a given string into three parts, parted by another given string.
; Character sequences in the original string, which are enclosed in pairs of comment chars
; (double quote, single quote, back quote) will be skipped and left untouched.
;
; Example 1 (split):                           Example 2 (no split):
; strString    = "This is a test string."      strString    = "This is a `test` string."
; strSplit     = " test"                       strSplit     = "`test`"
; arrResult[0] = "This is a"                   arrResult[0] = "This is a `test` string."
; arrResult[1] = " test"                       arrResult[1] = "`test`"
; arrResult[2] = " string."                    arrResult[2] = ""
;
; 20110203.Detlev Dalitz.
;..........................................................................................................................................
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------


;------------------------------------------------------------------------------------------------------------------------------------------
#DefineFunction udfItemLocateIndexWild (strPattern, strItemList, intStart, strDelimiter)
intCount = ItemCount (strItemList, strDelimiter)
For intPos = intStart To intCount
   If StrIndexWild (ItemExtract (intPos, strItemList, strDelimiter), strPattern, 1) == 1 Then Return intPos
Next
Return 0
;..........................................................................................................................................
; This UDF udfItemLocateIndexWild uses a given pattern string 'strPattern' to search for a similar item in a given itemlist 'strItemList'
; and returns the index position of the first similar item.
;
; The start position for the search is given by parameter 'intStart'.
; For repeated searching increment the parameter 'intStart' accordingly.
;
; Meaning of pattern symbols: '*' matches zero or more characters, '?' matches any one character.
;
; 20110203.Detlev Dalitz.
;..........................................................................................................................................
#EndFunction
;------------------------------------------------------------------------------------------------------------------------------------------


; Define constants.
strMsgTitle = "Embed UDF Syntax Template Into WBS Menu"
strMsgTitleTerm = strMsgTitle : " - Terminated"
strMsgTextTerm = "Not found ..." : @LF : 'UDF statement "#Define..."' : @LF : @LF : "Try again!"

strFolderHome = DirHome ()                        ; WinBatch system folder.
strFolderUDF = strFolderHome : "wsp-udf\"         ; Our working folder within WinBatch system folder.
strFolderBackup = strFolderHome : "Backup\"       ; Our backup folder within WinBatch system folder.
blnResult = DirMake (strFolderUDF)
blnResult = DirMake (strFolderBackup)

strFileWspMnu = strFolderHome : "wspopup.mnu"
strFileWspUdfWbt = strFolderUDF : "wsp-udf.wbt"   ; Use our working folder to hold our wbt file.
strFileWspUdfMnu = strFolderUDF : "wsp-udf.mnu"   ; Use our working folder to hold our menu file.
;strFileWspUdfWbt = strFolderHome : "wsp-udf.wbt" ; Use WinBatch system folder to hold our wbt file.
;strFileWspUdfMnu = strFolderHome : "wsp-udf.mnu" ; Use WinBatch system folder to hold our menu file.
strFileWspMnuBackupMask = "Backup.wspopup.mnu.{1}"
strFileWspUdfMnuBackupMask = "Backup.wsp-udf.mnu.{1}"

strM0 = ""     ; Wspopup menu level 0.
strM1 = " "    ; Wspopup menu level 1.
strM2 = "  "   ; Wspopup menu level 2.
strM3 = "   "  ; Wspopup menu level 3.
strM4 = "    " ; Wspopup menu level 4 is code area.


; Get the filename of the currently running script.
; Note: There is a failure with IntControl (77, 72, 0, 0, 0), the last item is missing, what is the filename.
; Note: 20110202 reported to WinBatch.
; Note: Ugly workaround. to detect the filename of the running script from within the Popup Menu system.
If RtStatus () == 10 ; WinBatch Studio debug.
   strFileThis = ItemExtract (4, ItemExtract (1, IntControl (77, 72, 0, 0, 0), @LF), @TAB)
   ; '1@TABcall@TAB13@TABCall (DirHome() : "wsp-udf\wsp-udf.wbt", strLine) @TAB'
   ; Structure stack position, structure type, line number, script line, file name
   strFileThis = ItemRemove (1, strFileThis, "(")
   strFileThis = ItemRemove (-1, strFileThis, ")")
   strFileThis = ItemRemove (-1, strFileThis, ",")
   strFileThis = "strFileThis=" : strFileThis
   Execute %strFileThis%
Else
   strFileThis = IntControl (1004, 0, 0, 0, 0)
   ; Pause ("RtStatus", RtStatus ())
EndIf
; Pause ("Debug", strFileThis)

; Put a copy of this file into our system related working folder.
If FileExist (strFileWspUdfWbt) == 0 Then blnResult = FileCopy (strFileThis, strFileWspUdfWbt, @FALSE)
If FileTimeCode (strFileThis) > FileTimeCode (strFileWspUdfWbt)
   blnResult = FileCopy (strFileThis, strFileWspUdfWbt, @FALSE)
EndIf


; Get parameter from clipboard or command line.
If !Param0
   ; Get Clipboard content. Clipboard can have other format than text, so catch the error.
   intEMLast = ErrorMode (@OFF)
   LastError ()
   strUdf = ClipGet ()
   intLastError = LastError ()
   ErrorMode (intEMLast)
   Drop (intEMLast)
   Terminate (intLastError > 0, strMsgTitleTerm : " (1)", strMsgTextTerm)
   strUdf = StrTrim (strUdf)
   Terminate (StrIndex (strUdf, "#", 1, @FWDSCAN) != 1, strMsgTitleTerm : " (2)", strMsgTextTerm)
Else
   strUdf = Param1
   ; Pause ("Param1",strUdf)
EndIf
ClipPut ("") ; Clear ClipBoard.


; Check line for keyword starting with "#Define".
strUdf = ItemExtract (1, strUdf, @CR)
strUdf = ItemExtract (-1, strUdf, @LF)
Terminate (StrIndexWild (ItemExtract (1, strUdf, " "), "#define", 1) != 1, strMsgTitleTerm : " (3)", strMsgTextTerm)
strUdf = StrTrim (ItemRemove (1, strUdf, " "))
arrResult = udfStrSplitAtStr (strUdf, ";")
strUdf = StrTrim (arrResult[0])
strComment = StrTrim (arrResult[2])


; Prepare list of existing menu entries from "wsp-udf.mnu".
strWspUdfs = ""
strSubMenus = ""
If FileExist (strFileWspUdfMnu) == 1
   strWspUdfs = FileGet (strFileWspUdfMnu)
   strWspUdfs = StrReplace (strWspUdfs, @CRLF, @LF)
   intCountUdf = ItemCount (strWspUdfs, @LF)
   For intI = 1 To intCountUdf
      strItem = StrTrim (ItemExtract (intI, strWspUdfs, @LF))
      If strItem == "" Then Continue
      strSubMenu = ItemExtract (1, strItem, "^")
      If !ItemLocate (strSubMenu, strSubMenus, @TAB) Then strSubMenus = ItemInsert (strSubMenu, -1, strSubMenus, @TAB)
   Next
   ; If no comment is given, then get existing comment, if any.
   If strComment == ""
      intPos = udfItemLocateIndexWild ("*^" : strUdf : "*", strWspUdfs, 1, @LF)
      If intPos
         strComment = ItemExtract (intPos, strWspUdfs, @LF)
         strComment = ItemExtract (-1, strComment, "^")
      EndIf
   EndIf
EndIf


; Ask for a menu entry from the existing entries or let create a new menu entry.
strNew = "___New_Submenu___"
strAskTitle = "Select Submenu ..."
strSubMenus = ItemInsert (strNew, 1, strSubMenus, @TAB)
IntControl (28, 1, 0, 0, 0) ; p1=1 fixed pitch font.
strSubMenu = AskItemlist (strAskTitle, strSubMenus, @TAB, @SORTED, @SINGLE, @TRUE) ; ToDo: CANCEL processing.

If strSubMenu == strNew
   strPrompt = "Enter Menu Title ..."
   IntControl (28, 1, 0, 0, 0) ; p1=1 fixed pitch font.
   strSubMenu = AskLine (strMsgTitle, strPrompt, "") ; ToDo: CANCEL processing.
   strSubMenu = StrTrim (strSubMenu)
EndIf
strSubMenuNew = strSubMenu

; Enter comment for the new function entry.
strPrompt = "Enter Comment ..." : @LF : @LF : strUdf
IntControl (28, 1, 0, 0, 0) ; p1=1 fixed pitch font.
strComment = AskLine (strMsgTitle, strPrompt, strComment) ; ToDo: CANCEL processing.
strComment = StrTrim (strComment)


; Update "wsp-udf.mnu".
strEntry = strSubMenuNew : "^" : strUdf : "^" : strComment
intPos = udfItemLocateIndexWild (ItemExtract (1, strEntry, "(") : "*", strWspUdfs, 1, @LF)
If intPos
   strWspUdfs = ItemRemove (intPos, strWspUdfs, @LF)
EndIf
strWspUdfs = ItemInsert (strEntry, -1, strWspUdfs, @LF)
strWspUdfs = ItemSort (strWspUdfs, @LF)
strWspUdfs = StrReplace (strWspUdfs, @LF, @CRLF)
FilePut (strFileWspUdfMnu, strWspUdfs)


; Update "wspopup.mnu".
; Allow changes to make into "wspopup.mnu".
strEntry = StrReplace (StrReplace (strEntry, @TAB, @LF), "^", @LF)
strPrompt = 'Insert this entry into "wspopup.mnu"?' : @LF : @LF : strEntry
Terminate (@YES != AskYesNo (strMsgTitle, strPrompt), strMsgTitleTerm : " (4)", "No changes." : @LF : @LF : "Ready.")

; Copy current "wspopup.mnu" file to Backup folder.
If FileExist (strFileWspMnu)
   strFileWspMnuBackup = StrReplace (strFileWspMnuBackupMask, "{1}", StrReplace (TimeYmdHms (), ':', ''))
   blnResult = FileCopy (strFileWspMnu, strFolderBackup : strFileWspMnuBackup, @FALSE)
EndIf

; Copy current "wsp-udf.mnu" file to Backup folder.
If FileExist (strFileWspUdfMnu)
   strFileWspUdfMnuBackup = StrReplace (strFileWspUdfMnuBackupMask, "{1}", StrReplace (TimeYmdHms (), ':', ''))
   blnResult = FileCopy (strFileWspUdfMnu, strFolderBackup : strFileWspUdfMnuBackup, @FALSE)
EndIf


; Prepare block markers.
strTagBase = "Insert WIL Function"
strTagBegin = ";[>>> Begin === User Defined Menu Entries ===]"
strTagEnd = ";[<<< End === User Defined Menu Entries ===]"

strTagBaseWild = strTagBase : "*"
strTagBeginWild = strTagBegin : "*"
strTagEndWild = strTagEnd : "*"

; Get current "wspopup.mnu".
strWspMnu = FileGet (strFileWspMnu)

; Find current block markers.
;intTagBase = udfItemLocateIndexWild (strTagBaseWild, strWspMnu, 1, @LF) ; For sure.
intTagBase = udfItemLocateIndexWild (strTagBaseWild, strWspMnu, 145, @LF) ; In practice.
If !StrIndex (ItemExtract (intTagBase + 1, strWspMnu, @LF), strTagBegin, 1, @FWDSCAN)
   strWspMnu = ItemInsert (strTagEnd : @CR, intTagBase, strWspMnu, @LF)
   strWspMnu = ItemInsert (strTagBegin : @CR, intTagBase, strWspMnu, @LF)
EndIf

; Find block edges.
intTagBegin = udfItemLocateIndexWild (strTagBeginWild, strWspMnu, intTagBase + 1, @LF)
intTagEnd = udfItemLocateIndexWild (strTagEndWild, strWspMnu, intTagBegin + 1, @LF)

; Create new block entry.
strUdfBlockNew = " User Defined...." : @CR
strWspUdfs = StrReplace (strWspUdfs, @CR, "")
intCountUdf = ItemCount (strWspUdfs, @LF)
strSubMenuPrev = ""
For intI = 1 To intCountUdf
   strItem = ItemExtract (intI, strWspUdfs, @LF)
   If strItem == "" Then Continue
   strSubMenu = ItemExtract (1, strItem, "^")
   If strSubMenu != strSubMenuPrev
      strUdfBlockNew = ItemInsert (strM2 : strSubMenu : "...." : @CR, -1, strUdfBlockNew, @LF)
      strSubMenuPrev = strSubMenu
   EndIf
   strUdf = ItemExtract (2, strItem, "^")
   strComment = ItemExtract (-1, strItem, "^")
   strUdfName = StrTrim (ItemExtract (1, strUdf, "("))
   strUdfParms = "(" : ItemExtract (2, strUdf, "(")
   strLine = strM3 : strUdfName : " ;^" : strUdfName : "^" : strUdfParms
   strLine1 = strLine : "^" : strComment
   strUdfBlockNew = ItemInsert (strLine1 : @CR, -1, strUdfBlockNew, @LF)
   strLine2 = strM4 : 'Call(zxc,"' : strUdfName : '")'
   strUdfBlockNew = ItemInsert (strLine2 : @CR, -1, strUdfBlockNew, @LF)
Next

; Get old block.
strUdfBlockOld = ""
intFirst = intTagBegin + 1
intLast = intTagEnd - 1
For intI = intFirst To intLast
   strUdfBlockOld = ItemInsert (ItemExtract (intI, strWspMnu, @LF), -1, strUdfBlockOld, @LF)
Next

; If old block is the same as the new block, then exit.
Terminate (strUdfBlockOld == strUdfBlockNew, strMsgTitleTerm : " (5)", "Ready. (No changes)")

; Remove old block.
For intI = intFirst To intLast
   strWspMnu = ItemRemove (intTagBegin + 1, strWspMnu, @LF)
Next

; Insert new block.
intCount = ItemCount (strUdfBlockNew, @LF)
For intI = intCount To 1 By -1
   strItem = ItemExtract (intI, strUdfBlockNew, @LF)
   strWspMnu = ItemInsert (strItem, intTagBegin, strWspMnu, @LF)
Next

; Write new file "wspopup.mnu".
FilePut (strFileWspMnu, strWspMnu)

; Display what line has been added.
strMsgText = "Added menu entry:" : @LF : @LF : strEntry : @LF : @LF : "Ready." : @LF : @LF : "(WIL menus will be reloaded on the next usage.)"
Message (strMsgTitle, strMsgText)

:CANCEL
Exit


; Test cases.
#DefineFunction A_Function (Param1, Param2) ; Any Comment.
#EndFunction
#DefineFunction B_Function (Param1) ; Any Comment.
#EndFunction
#DefineFunction F_Function (Param1, Param2, Param3) ; Any Comment about F_Function.
#EndFunction