Updated 2011-12-29 06:56:10 by RLE

LES on 24 Feb 2007 - Here is the situation: I use Tkabber to chat on MSN, ICQ, IRC etc., but sometimes I feel like using SIM [1] [2], another multi-platform instant messenger, for the following reasons:

- it can send and receive files;

- it uses the protocols/servers directly while Tkabber forces me to use Jabber "bridges" that go down and break my connection a bit too often;

- it displays smileys correctly - that really shouldn't matter, but people insist on posting smileys and they're a bit tired of me asking "what was that funny string you just sent me?";

- it has OSD and that saves me a lot of the Alt+Tabbing that Tkabber often forces me to do just to discover that that last comment from the other party was particularly not alt-tab-worthy;

- SIM looks a lot better overall than Tkabber.

Two things have made me stick to Tkabber:

1) Knowing that I can hack into Tkabber's source because it is written in a language I know always gives me that warm fuzzy feeling.

2) I actually have hacked into Tkabber because I absolutely refuse to chat without a decent "auto correct" implementation. I have discussed this here already (see: AutoCorrect).

SIM does have "auto correct", even if it's just a half decent implementation. It is a plugin called "Replace text". It doesn't react to punctuation, but it reacts to space and, well, that's better than nothing. The biggest hurdle is the management interface. Although it offers a nice GUI for insertion and removal of entries, it offers no way to import an existing auto correct list. That has to be a problem because the auto correct list I have been using for eight years and currently in at least three applications has 13,595 entries, no less. I really need an import feature. But SIM has to keep its own list somewhere, doesn't it? I certainly can edit it, can't I? Well, I ran into a few other hurdles:

1) I keep my auto correct list centralized in one single file. That list is updated very often, so I need to be able to automate this synchronization with the SIM plugin configuration file. SIM keeps the configuration of ALL its plugins in one single file, in a "Replace" section, in a very similar arrangement to that of Windows' .ini files [3]. I use Linux, but I imagine that SIM started on Windows and came to Linux afterwards. So, editing that file non-interactively requires some care with a lot of other data that is kept in the same file.

2) My auto correct list is neat [4]: key=value, one pair in each line. Very easy to parse. SIM's Replace plugin stores its aliases in two lists: ALL THE KEYS followed by ALL THE VALUES. Additionally, each KEY must be accompanied by its corresponding number that serves as some sort of primary key of a hash or database-like structure. Accordingly, every VALUE must be accompanied by that number, matching the number of its corresponding KEY.

3) Finally, the second line of that "Replace" section must declare the number of auto correct entries. So I have to count them so I can update that line correctly.

This is it. You know the motive, you know the weapon, now here is the crime:
 #!/usr/bin/env tclsh
 # Modify the following with the file that contains your auto correct files:
 set ::ALIASFILE "/home/$env(USER)/autocorrect.txt"
 # Modify the following with the file that contains your SIM config file:
 set ::SIMFILE "/home/$env(USER)[email protected]/plugins.conf"
 # This script should work correctly on any Linux or Unix-like operating 
 # system, but probably won't work on other platforms without a few tweaks 
 # which I currently have no motivation to apply and test etc.
 # Do not modify anything below this point unless you know what you're doing, 
 # i.e. altering the source.
 # ================================================
 # GLOBALS used in this script:
 # This script doesn't have any procs so everything is global, actually. 
 # I just created these "globals" to make them more visible than other vars.
 # ================================================
 # ================================================
 # open ALIASFILE and parse each line. If the line matches a certain 
 # regular expression, the regex itself will pick the "key" and the "value", 
 # respectively. They're added to two Tcl lists: SIMKEYS and SIMVALUES. 
 # Finally, the number of aliases is counted.
 # ================================================
 set  _aliascount  0
 set  _regex  {([^=]+)\s*=\s*(.*)}
 set  _fp  [ open  $::ALIASFILE  r ]
         while         { [ eof  $_fp ] == 0 }        { 
                 set _line [ string trim [ gets $_fp ] ]
 #                # Exclude empty lines from the alias file
                 if          { [ regexp  {^$}  $_line ] }          { continue }
 #                # Exclude commented out lines in the alias file
                 if          { [ regexp  {^#.*$}  $_line ] }          { continue }
                 regexp  $_regex  $_line  =>  _key  _value
 #                # For example: absm=absolutamente; key=value
                 lappend  ::SIMKEYS  [ string trim  $_key ]
                 lappend  ::SIMVALUES  [ string trim  $_value ]
                 incr  _aliascount
 close $_fp
 # We'll need to know the number of aliases in the next section
 set  ::LINE2  "Keys=$_aliascount"
 # ================================================
 # open SIMFILE and parse each line. 
 # If line == [replace], this marks the beginning of the section we are going 
 # to "rewrite", or rather buffer in the NEWCONFIG global variable. So we 
 # buffer the "[replace]" mark (it is lost if not added explicitly) plus two 
 # required lines after that: LINE1 is whatever comes right after the 
 # "[replace]" mark. LINE2 is an indicator of the number of aliases the 
 # Replace plug-in should expect. Then we build the new list of keys and 
 # values and buffer that too. Finally, we flag up _ignore. That means we're 
 # parsing (and ignoring) old alias lines. If _ignore is flagged up, a regular 
 # expression tries to determine when the old alias section is over. When it 
 # is over, _ignore is flagged down again so the next iteration drops right 
 # to the last and most frequently used option (Option 3), which is adding the 
 # current line to NEWCONFIG.
 # ================================================
 set _ignore "no"
 set  _fp  [ open  $::SIMFILE  r ]
         while         { [ eof  $_fp ] == 0 }        { 
                 set _line [ string trim [ gets $_fp ] ]
 #                # Option 1
 #                # CONFIG starts at the line with "[replace]"
                 if          { $_line == {[replace]} }          { 
                         append  ::NEWCONFIG  "\[replace\]\n"
 #                        # The very next line is required
                         set  ::LINE1  [ string trim [ gets $_fp ] ]
                         append  ::NEWCONFIG  "$::LINE1\n"
                         append  ::NEWCONFIG  "$::LINE2\n"
                         for { set i 0 }  { $i < [ llength $::SIMKEYS ] }  { incr i }          { 
                                 set k [ expr {$i + 1} ]
                                 append ::NEWCONFIG "Key=${k},\"[ lindex $::SIMKEYS $i ]\"\n"
                         for { set i 0 }  { $i < [ llength $::SIMVALUES ] }  { incr i }         { 
                                 set k [ expr {$i + 1} ]
                                 append ::NEWCONFIG "Value=${k},\"[ lindex $::SIMVALUES $i ]\"\n"
                         set  _ignore  "yes"
 #                # Option 2
 #                # CONFIG ends at the line with "[something else]"
                 if          { $_ignore == "yes" }                  { 
                         if          {[ regexp  {^\[.+?\]$}  $_line  => ]}          { 
                                 append  ::NEWCONFIG  "$_line\n"
                                 set _ignore "no"
 #                # ignore existing alias section
                 if          { $_ignore == "yes" }                  { continue }
 #                # Option 3
                 append  ::NEWCONFIG  "$_line\n"
         }; # end while
 close $_fp
 # ----------------------------------------------------------------
 # Overwrite SIMFILE with the NEWCONFIG "buffer".
 set  _fp  [ open  $::SIMFILE  w ]
         puts  $_fp  $::NEWCONFIG
 close $_fp
 puts "done!"