Update process line numbers with AutoHotKey
- Jul. 26, 2020
Introduction
If you are like me, you probably also tend to update TI processes in a text editor like Notepad++, rather than the clients that the old TI editor. Whenever we are in development stages of a new model or a bunch of TI processes, it's just easier and faster. You would open up the *.pro file in Notepad++ or similar, make code edits and save the file. HOWEVER, there are 2 main caveats:
- you must restart the TM1 model for TM1 to pick up the changed process code. Code in TI processes is only captured at TM1 server startup. Alternatively, use a tool or the REST API to hot promote the changed/new TI process without bouncing the server.
- the *.pro file (opened in a text editor) contains line numbers and certain ID's, such that the TI editor can correcly interpret the different pieces of information in the file
For example, here's a small part from a Bedrock TI process opened in Notepad++:
The selected lines all have a code (3 digits), followed by a comma, followed by the number of lines of information/code that follows. The information that follows pertains to the area of the process marked by the ID. For instance: 560,3 means that the process has 3 parameters and the parameter names. 637,3 marks the 3 prompts, 1 for each parameter. 577 until 581 deal with variables in the TI process, in this case there aren't any such variables. 572-573-574-575 are very important too: they will contain the number of lines of code in the Prolog tab, Metadata tab, Data tab, Epilog tab, respectively. Hence, whenever we change a line of code in a TI process and it means that we have more or less lines in either tab, we need to update the line number count. If not, or if the number that follows the comma is wrong, we risk losing code ! Be cautious there.
As I've been bitten myself a number of times, and just because it's such a stupid and error-prone task to do manually, I decided to automate it with AutoHotKey. It's not terribly difficult and, luckily for you, I will share my code below. Here it is.
if WinActive("ahk_class Notepad++") { Send ^s Sleep 100 WinMenuSelectItem, , , Edit, Copy to Clipboard, Current Full File path to Clipboard Sleep 100 vFullFilename := Clipboard FileRead, FileContents, %vFullFilename% ; Parameters Nr_of_lines_01 := CountLines( StringBetween( FileContents, "560,", "561," ) ) - 2 ; Variables Nr_of_lines_02 := CountLines( StringBetween( FileContents, "577,", "578," ) ) - 2 ; Prolog tab Nr_of_lines_03 := CountLines( StringBetween( FileContents, "572,", "573," ) ) - 2 ; Metadata tab Nr_of_lines_04 := CountLines( StringBetween( FileContents, "573,", "574," ) ) - 2 ; Data tab Nr_of_lines_05 := CountLines( StringBetween( FileContents, "574,", "575," ) ) - 2 ; Epilog tab Nr_of_lines_06 := CountLines( StringBetween( FileContents, "575,", "576," ) ) - 2 ; update the PRO file Loop, Read, %vFullFilename%, NewFile.txt { NewData := ; Parameter names If ( InStr( A_LoopReadLine, "560," ) = 1 ) NewData := "560," . Nr_of_lines_01 ; Parameter types else if ( InStr( A_LoopReadLine, "561," ) = 1 ) NewData := "561," . Nr_of_lines_01 ; Parameter defaults else if ( InStr( A_LoopReadLine, "590," ) = 1 ) NewData := "590," . Nr_of_lines_01 ; Parameter names else if ( InStr( A_LoopReadLine, "637," ) = 1 ) NewData := "637," . Nr_of_lines_01 ; Variables else if ( InStr( A_LoopReadLine, "577," ) = 1 ) NewData := "577," . Nr_of_lines_02 ; Variables else if ( InStr( A_LoopReadLine, "578," ) = 1 ) NewData := "578," . Nr_of_lines_02 ; Variables else if ( InStr( A_LoopReadLine, "579," ) = 1 ) NewData := "579," . Nr_of_lines_02 ; Variables else if ( InStr( A_LoopReadLine, "580," ) = 1 ) NewData := "580," . Nr_of_lines_02 ; Variables else if ( InStr( A_LoopReadLine, "581," ) = 1 ) NewData := "581," . Nr_of_lines_02 ; Variables else if ( InStr( A_LoopReadLine, "582," ) = 1 ) NewData := "582," . Nr_of_lines_02 ; Prolog tab else if ( InStr( A_LoopReadLine, "572," ) = 1 ) NewData := "572," . Nr_of_lines_03 ; Metadata tab else if ( InStr( A_LoopReadLine, "573," ) = 1 ) NewData := "573," . Nr_of_lines_04 ; Data tab else if ( InStr( A_LoopReadLine, "574," ) = 1 ) NewData := "574," . Nr_of_lines_05 ; Epilog tab else if ( InStr( A_LoopReadLine, "575," ) = 1 ) NewData := "575," . Nr_of_lines_06 else NewData := % A_LoopReadLine FileAppend, % NewData "`r`n" } FileDelete, %vFullFilename% FileMove, NewFile.txt, %vFullFilename% WinMenuSelectItem, , , File, Reload from Disk } else MsgBox Please use this tool only when an TI process is open in Notepad++ Return
What does this code do ?
- first it checks whether you are working in Notepad++ when you launch the tool. If that is not the case, a MsgBox will pop up. You can leave out this test if wanted.
- We send a Save command (Ctrl + s) is simulated
- The AHK code waits for 1000 ms, a fraction of second, to give time to the previous command to finish
- Then we copy the current filename of the *.pro file (including its full path) to the clipboard. The filename, as you know, contains the name of the process.
- Again let's give Notepad++ some time to breath.
- We retrieve the full file name from the clipboard, which happens to be a variable in AutoHotKey.
- FileRead will allow us to store the file contents into a variable, which we will dissect in the next lines of code.
- With an own custom function, we will grab the text between 2 successive codes. That function is called StringBetween.
- Using a second custom function by ourselves, we will count the number of lines of the text we grabbed in the previous step. This function is called CountLines.
- We store the result in a variable. We execute this sequence for the parameters, the variables, and the code in Prolog/Metadata/Data/Epilog, hence 6 variables in AutoHotKey.
- Now we are going to create and fill a temporary text file, called 'NewFile.txt'. It will contain the contents of the *.pro file, line after line, but then we update the line number count at several places.
- Hence, we use Loop to loop through the lines of the *.pro file, it's the variable vFullFilename%. We also keep that 'NewFile.txt' file opened to be able to append text near the end of the file as we treat the different lines of the *.pro file. That temporary file is located in the Working directory of AutoHotKey.
- Basically, we copy the information from the *.pro file to the NewFile.txt file, except for codes equal to:
- 560-637: information on the parameters
- 577-582: information on the variables
- 572: the Prolog tab code
- 573: the Metadata tab code
- 574: the Data tab code
- 575: the Epilog tab code
- When we have all information, let's append it to the text file.
- When the loop over lines is over let's delete the *.pro file and move (rename) the populated NewFile.txt file as the full file name of the process.
- Finally, reload the file such that for the user (you !) it is as if nothing has happened, only updated line count numbers are visible :-)
It's worthwile to note that I have Notepad++ in English. If you use a different language for the interface, then the names for the menus that we used in the code could be different. See the code of WinMenuSelectItem. You should update this part accordingly.
Obviously, I still need to give you the code for my 2 custom functions. It's a lot less code compared to what you have already investigated above:
CountLines(Text) { StringReplace, Text, Text, `n, `n, UseErrorLevel Return ErrorLevel + 1 } StringBetween( String, NeedleStart, NeedleEnd ) { StringGetPos, pos, String, % NeedleStart If ( ErrorLevel ) Return "" StringTrimLeft, String, String, pos + StrLen( NeedleStart ) If ( NeedleEnd = "" ) Return String StringGetPos, pos, String, % NeedleEnd If ( ErrorLevel ) Return "" StringLeft, String, String, pos Return String }
Have fun !