Update process line numbers with AutoHotKey


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
                    NewData := % A_LoopReadLine

                FileAppend, % NewData "`r`n"
            FileDelete, %vFullFilename%
            FileMove, NewFile.txt, %vFullFilename%
            WinMenuSelectItem, , , File, Reload from Disk
           MsgBox Please use this tool only when an TI process is open in Notepad++

What does this code do ?

  1. 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.
  2. We send a Save command (Ctrl + s) is simulated
  3. The AHK code waits for 1000 ms, a fraction of second, to give time to the previous command to finish
  4. 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.
  5. Again let's give Notepad++ some time to breath.
  6. We retrieve the full file name from the clipboard, which happens to be a variable in AutoHotKey.
  7. FileRead will allow us to store the file contents into a variable, which we will dissect in the next lines of code.
  8. With an own custom function, we will grab the text between 2 successive codes. That function is called StringBetween.
  9. 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.
  10. 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.
  11. 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.
  12. 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.
  13. Basically, we copy the information from the *.pro file to the NewFile.txt file, except for codes equal to:
    1. 560-637: information on the parameters
    2. 577-582: information on the variables
    3. 572: the Prolog tab code
    4. 573: the Metadata tab code
    5. 574: the Data tab code
    6. 575: the Epilog tab code
  14. When we have all information, let's append it to the text file.
  15. 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.
  16. 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:

     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 !


Section contents

About Wim

Wim Gielis is a Business Intelligence consultant and Excel expert

Other links