<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.secondlife.com/w/index.php?action=history&amp;feed=atom&amp;title=User%3AAndrew_Linden%2FChatWick</id>
	<title>User:Andrew Linden/ChatWick - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.secondlife.com/w/index.php?action=history&amp;feed=atom&amp;title=User%3AAndrew_Linden%2FChatWick"/>
	<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=User:Andrew_Linden/ChatWick&amp;action=history"/>
	<updated>2026-06-08T14:52:53Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.42.1</generator>
	<entry>
		<id>https://wiki.secondlife.com/w/index.php?title=User:Andrew_Linden/ChatWick&amp;diff=1177480&amp;oldid=prev</id>
		<title>Andrew Linden: Created page with &quot;This is the latest version of the script that generates the wiki format text for the Simulator_User_Group transcripts.  &lt;pre&gt; #!/usr/bin/env python # # chatwick.py -- transla…&quot;</title>
		<link rel="alternate" type="text/html" href="https://wiki.secondlife.com/w/index.php?title=User:Andrew_Linden/ChatWick&amp;diff=1177480&amp;oldid=prev"/>
		<updated>2013-03-27T23:05:53Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;This is the latest version of the script that generates the wiki format text for the &lt;a href=&quot;/wiki/Simulator_User_Group&quot; title=&quot;Simulator User Group&quot;&gt;Simulator_User_Group&lt;/a&gt; transcripts.  &amp;lt;pre&amp;gt; #!/usr/bin/env python # # chatwick.py -- transla…&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;This is the latest version of the script that generates the wiki format text for the [[Simulator_User_Group]] transcripts.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
#!/usr/bin/env python&lt;br /&gt;
#&lt;br /&gt;
# chatwick.py -- translate chat logs into wiki format&lt;br /&gt;
#&lt;br /&gt;
# License = common domain&lt;br /&gt;
# &lt;br /&gt;
# Usage: chatwick [-c config_file] [-o output_file] chat_log_file&lt;br /&gt;
&lt;br /&gt;
import datetime&lt;br /&gt;
import optparse&lt;br /&gt;
import sys&lt;br /&gt;
import random&lt;br /&gt;
import re&lt;br /&gt;
import os.path&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
RED_SHIFT = 16&lt;br /&gt;
GREEN_SHIFT = 8&lt;br /&gt;
BLUE_SHIFT = 0&lt;br /&gt;
&lt;br /&gt;
TABLE_START = &amp;quot;{|\n&amp;quot;&lt;br /&gt;
TABLE_COLUMN_START = &amp;quot;|&amp;quot;&lt;br /&gt;
TABLE_ROW_END = &amp;quot;|-\n&amp;quot;&lt;br /&gt;
TABLE_END = &amp;quot;|}\n&amp;quot;&lt;br /&gt;
&lt;br /&gt;
DEFAULT_OBJECT_COLOR = &amp;quot;#808080&amp;quot;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def replace_link(match_obj):&lt;br /&gt;
    return &amp;quot;&amp;lt;/nowiki&amp;gt;%s&amp;lt;nowiki&amp;gt;&amp;quot; % match_obj.group(&amp;#039;link&amp;#039;)&lt;br /&gt;
    &lt;br /&gt;
&lt;br /&gt;
def angle_to_hex(angle, color):&lt;br /&gt;
    # Here is the the angle curve for red&lt;br /&gt;
    #     |&lt;br /&gt;
    # 255 +-------                               --------+&lt;br /&gt;
    #     |       \                             /&lt;br /&gt;
    #     |        \                           /&lt;br /&gt;
    #     |         \                         /&lt;br /&gt;
    #     |          \                       /&lt;br /&gt;
    #     |           \                     /&lt;br /&gt;
    #     |            \                   /&lt;br /&gt;
    #     |             \                 /&lt;br /&gt;
    #   0-+-------|------|=======|=======|-------|-------|--&lt;br /&gt;
    #     0      60     120     180     240     300     360&lt;br /&gt;
    #&lt;br /&gt;
    # Green and blue are the same curve, just shifted by 120 and 240 degrees.&lt;br /&gt;
    #&lt;br /&gt;
    offset = 0&lt;br /&gt;
    if color == &amp;#039;red&amp;#039;:&lt;br /&gt;
        pass&lt;br /&gt;
    elif color == &amp;#039;green&amp;#039;:&lt;br /&gt;
        offset = -120&lt;br /&gt;
    elif color == &amp;#039;blue&amp;#039;:&lt;br /&gt;
        offset = -240&lt;br /&gt;
    else:&lt;br /&gt;
        raise &amp;quot;Unknown primary color %s&amp;quot; % color&lt;br /&gt;
&lt;br /&gt;
    # clamp color_angle in range of [0, 360]&lt;br /&gt;
    color_angle = (angle + offset) % 360&lt;br /&gt;
    if color_angle &amp;lt; 0:&lt;br /&gt;
        color_angle += 360&lt;br /&gt;
    &lt;br /&gt;
    # compute hex value&lt;br /&gt;
    hex = 255;&lt;br /&gt;
    if color_angle &amp;lt; 60:&lt;br /&gt;
        pass&lt;br /&gt;
    elif color_angle &amp;lt; 120:&lt;br /&gt;
        hex = int(255.0 * (1.0 - (float(color_angle) - 60.0)/60.0))&lt;br /&gt;
    elif color_angle &amp;lt; 240:&lt;br /&gt;
        hex = 0&lt;br /&gt;
    elif color_angle &amp;lt; 300:&lt;br /&gt;
        hex = int(255.0 * ((float(color_angle) - 240.0)/60.0))&lt;br /&gt;
    return hex    &lt;br /&gt;
        &lt;br /&gt;
&lt;br /&gt;
def generate_colors(hues, saturations, max_saturation, min_saturation):&lt;br /&gt;
    colors = []&lt;br /&gt;
    hue_step = int(360 / hues)&lt;br /&gt;
    saturation_step = (max_saturation - min_saturation)/float(saturations)&lt;br /&gt;
    s = 0&lt;br /&gt;
    saturation = min_saturation&lt;br /&gt;
    while s &amp;lt; saturations:&lt;br /&gt;
        for angle in range(0, 360, hue_step):&lt;br /&gt;
            red = int(angle_to_hex(angle, &amp;#039;red&amp;#039;) * saturation)&lt;br /&gt;
            green = int(angle_to_hex(angle, &amp;#039;green&amp;#039;) * saturation)&lt;br /&gt;
            blue = int(angle_to_hex(angle, &amp;#039;blue&amp;#039;) * saturation)&lt;br /&gt;
            color_str = &amp;#039;#%02x%02x%02x&amp;#039; % (red, green, blue)&lt;br /&gt;
            color = (red &amp;lt;&amp;lt; RED_SHIFT) + (green &amp;lt;&amp;lt; GREEN_SHIFT) + (blue &amp;lt;&amp;lt; BLUE_SHIFT)&lt;br /&gt;
            #print &amp;#039;&amp;lt;font color=&amp;quot;%s&amp;quot;&amp;gt;%s&amp;lt;/font&amp;gt;\n&amp;#039; % (color_str, color_str)&lt;br /&gt;
            colors.append(color_str)&lt;br /&gt;
        saturation += saturation_step&lt;br /&gt;
        s += 1&lt;br /&gt;
    return colors&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def write_output(output_fp, line):&lt;br /&gt;
    if output_fp:&lt;br /&gt;
        output_fp.write(line)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
class LineProcessor:&lt;br /&gt;
    def __init__(self):&lt;br /&gt;
        self.configured_color_map = {}&lt;br /&gt;
        self.assigned_color_map = {}&lt;br /&gt;
        self.deletes = {}&lt;br /&gt;
        self.last_color = None&lt;br /&gt;
        self.available_colors = generate_colors(13, 4, 0.90, 0.4)&lt;br /&gt;
        self.special_colors = generate_colors(8, 1, 0.4, 0.3)&lt;br /&gt;
        self.prefix = []&lt;br /&gt;
        self.postfix = []&lt;br /&gt;
&lt;br /&gt;
        # [00:00] text&lt;br /&gt;
        self.re_timestamped_line = re.compile(&amp;#039;(?P&amp;lt;timestamp&amp;gt;^\[\d\d:\d\d\])\s+(?P&amp;lt;text&amp;gt;.*)$&amp;#039;)&lt;br /&gt;
        # User Name: text&lt;br /&gt;
        self.re_two_word_name_line = re.compile(&amp;#039;^(?P&amp;lt;username&amp;gt;\w+ \w+):\s+(?P&amp;lt;text&amp;gt;.*)$&amp;#039;)&lt;br /&gt;
        # User Name text&lt;br /&gt;
        self.re_me_line = re.compile(&amp;#039;^(?P&amp;lt;username&amp;gt;\w+ \w+)\s+(?P&amp;lt;text&amp;gt;.*)$&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
        # UserName: text&lt;br /&gt;
        self.re_one_word_name_line = re.compile(&amp;#039;^(?P&amp;lt;username&amp;gt;\w+):\s+(?P&amp;lt;text&amp;gt;.*)$&amp;#039;)&lt;br /&gt;
        # UserName text&lt;br /&gt;
&lt;br /&gt;
        # Object with arbitrarily long name: text&lt;br /&gt;
        self.re_object_line = re.compile(&amp;#039;^(?P&amp;lt;objectname&amp;gt;.*?):\s+(?P&amp;lt;text&amp;gt;.+)$&amp;#039;)&lt;br /&gt;
        # [0000/00./00 00:00] anything&lt;br /&gt;
        self.re_old_timestamp = re.compile(&amp;#039;^\[\d{4,4}/\d\d/\d\d \d\d:\d\d]\s+.*$&amp;#039;)&lt;br /&gt;
        # Something Linden&lt;br /&gt;
        self.re_special_name = re.compile(&amp;#039;^\w+ Linden$&amp;#039;)&lt;br /&gt;
        # http://something&lt;br /&gt;
        self.re_http_link = re.compile(&amp;#039;(?P&amp;lt;link&amp;gt;https?://[-_:/=\w\.\?\$]+)&amp;#039;)&lt;br /&gt;
        # &amp;lt;nowiki&amp;gt; &amp;lt;/nowiki&amp;gt;&lt;br /&gt;
        self.re_useless_nowiki = re.compile(&amp;#039;&amp;lt;nowiki&amp;gt;\s*&amp;lt;/nowiki&amp;gt;&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    def listColors(self):&lt;br /&gt;
    # debug method&lt;br /&gt;
        for color in self.available_colors:&lt;br /&gt;
            print &amp;#039;&amp;lt;font color=&amp;quot;%s&amp;quot;&amp;gt;&amp;lt;b&amp;gt;%s&amp;lt;/b&amp;gt; available color&amp;lt;/font&amp;gt;\n&amp;#039; % (color, color)&lt;br /&gt;
        for color in self.special_colors:&lt;br /&gt;
            print &amp;#039;&amp;lt;font color=&amp;quot;%s&amp;quot;&amp;gt;&amp;lt;b&amp;gt;%s&amp;lt;/b&amp;gt; special color&amp;lt;/font&amp;gt;\n&amp;#039; % (color, color)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    def readConfig(self, file):&lt;br /&gt;
    #   All config lines have the following format: &lt;br /&gt;
    #       command &amp;#039;User Name&amp;#039; [option]&lt;br /&gt;
    #   Currently supported commands are:&lt;br /&gt;
    #       color &amp;#039;User Name&amp;#039; colorname&lt;br /&gt;
    #       delete &amp;#039;User Name&amp;#039;&lt;br /&gt;
    #       prefix &amp;#039;prefix line&amp;#039;&lt;br /&gt;
    #       postfix &amp;#039;postfix line&amp;#039;&lt;br /&gt;
    # The prefix/postfix commands will pre/postpend the output with those lines,&lt;br /&gt;
    # in the order they are given.  Some ALL_CAPS keywords in the pre/postfix &lt;br /&gt;
    # lines will be replaced (if applicable).  See the implementation of&lt;br /&gt;
    # replaceKeywordsInConfig().&lt;br /&gt;
        fp = open(file)    &lt;br /&gt;
        line = fp.readline()&lt;br /&gt;
        while line:&lt;br /&gt;
            clean_line = line.strip()&lt;br /&gt;
            line = fp.readline()&lt;br /&gt;
            if len(clean_line) == 0:&lt;br /&gt;
                continue&lt;br /&gt;
            if clean_line[0] == &amp;#039;#&amp;#039;:&lt;br /&gt;
                continue&lt;br /&gt;
            [key, value] = clean_line.split(None, 1)&lt;br /&gt;
    &lt;br /&gt;
            words = value.split(&amp;quot;&amp;#039;&amp;quot;)&lt;br /&gt;
            if len(words) != 3:&lt;br /&gt;
                words = value.split(&amp;#039;&amp;quot;&amp;#039;)&lt;br /&gt;
            if len(words) != 3:&lt;br /&gt;
                continue&lt;br /&gt;
    &lt;br /&gt;
            if key == &amp;quot;color&amp;quot;:&lt;br /&gt;
                assigned_color = words[2].strip().lower()&lt;br /&gt;
                self.configured_color_map[words[1]] = assigned_color&lt;br /&gt;
            elif key == &amp;quot;delete&amp;quot;:&lt;br /&gt;
                self.deletes[words[1]] = 1&lt;br /&gt;
            else:&lt;br /&gt;
                value = value.strip(&amp;quot;\&amp;quot;&amp;#039;&amp;quot;)&lt;br /&gt;
                if key == &amp;quot;prefix&amp;quot;:&lt;br /&gt;
                    self.prefix.append(value)&lt;br /&gt;
                elif key == &amp;quot;postfix&amp;quot;:&lt;br /&gt;
                    self.postfix.append(value)&lt;br /&gt;
        if not self.configured_color_map.has_key(&amp;#039;Object&amp;#039;):&lt;br /&gt;
            # by default objects show up as green&lt;br /&gt;
            self.configured_color_map[&amp;#039;Object&amp;#039;] = DEFAULT_OBJECT_COLOR&lt;br /&gt;
&lt;br /&gt;
        # remove matching assigned colors from available and special colors&lt;br /&gt;
        configured_colors = self.configured_color_map.values()&lt;br /&gt;
        for color in configured_colors:&lt;br /&gt;
            index = 0&lt;br /&gt;
            while index &amp;lt; len(self.available_colors):&lt;br /&gt;
                if color == self.available_colors[index]:&lt;br /&gt;
                    del(self.available_colors[index])&lt;br /&gt;
                else:&lt;br /&gt;
                    index += 1&lt;br /&gt;
            index = 0&lt;br /&gt;
            while index &amp;lt; len(self.special_colors):&lt;br /&gt;
                if color == self.special_colors[index]:&lt;br /&gt;
                    del(self.special_colors[index])&lt;br /&gt;
                else:&lt;br /&gt;
                    index += 1&lt;br /&gt;
        fp.close()&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    def replaceKeywordsInConfig(self, input_file):&lt;br /&gt;
    # The following keywords will be replaced in the prefix and postfix lines:&lt;br /&gt;
    #       THIS_WEEK = &amp;quot;YYYY.MM.DD&amp;quot; for the date in the input_filename&lt;br /&gt;
    #       PREV_WEEK = &amp;quot;YYYY.MM.DD&amp;quot; for one week prior to the date in the infput_filename (&amp;quot;)&lt;br /&gt;
    #       NEXT_WEEK = &amp;quot;YYYY.MM.DD&amp;quot; for one week after to the date in the infput_filename (&amp;quot;)&lt;br /&gt;
    # Note: these only work if the input_file name contains the date in one of&lt;br /&gt;
    # the following formats:&lt;br /&gt;
    #       YYYYMMDD&lt;br /&gt;
    #       YYYY.MM.DD (where . can be any character)&lt;br /&gt;
    #&lt;br /&gt;
        # get date from input_file&lt;br /&gt;
        this_week = None&lt;br /&gt;
        simple_date = re.search(&amp;#039;20\d\d\d\d\d\d&amp;#039;, input_file)&lt;br /&gt;
        if simple_date:&lt;br /&gt;
            date_str = input_file[simple_date.start():simple_date.end()]&lt;br /&gt;
            year = int(date_str[0:4])&lt;br /&gt;
            month = int(date_str[4:6])&lt;br /&gt;
            day = int(date_str[6:8])&lt;br /&gt;
            this_week = datetime.date(year, month, day)&lt;br /&gt;
        else:&lt;br /&gt;
            fancy_date = re.search(&amp;#039;20\d\d.\d\d.\d\d&amp;#039;, input_file)&lt;br /&gt;
            if fancy_date:&lt;br /&gt;
                date_str = input_file[fancy_date.start():fancy_date.end()]&lt;br /&gt;
                year = int(date_str[0:4])&lt;br /&gt;
                month = int(date_str[5:7])&lt;br /&gt;
                day = int(date_str[8:10])&lt;br /&gt;
                this_week = datetime.date(year, month, day)&lt;br /&gt;
        if this_week:&lt;br /&gt;
            one_week = datetime.timedelta(days=7)&lt;br /&gt;
            last_week = this_week - one_week&lt;br /&gt;
            next_week = this_week + one_week&lt;br /&gt;
            this_week_str = str(this_week)&lt;br /&gt;
            last_week_str = str(last_week)&lt;br /&gt;
            next_week_str = str(next_week)&lt;br /&gt;
            while re.search(&amp;#039;-&amp;#039;, this_week_str):&lt;br /&gt;
                this_week_str = re.sub(&amp;#039;-&amp;#039;, &amp;#039;.&amp;#039;, this_week_str)&lt;br /&gt;
            while re.search(&amp;#039;-&amp;#039;, last_week_str):&lt;br /&gt;
                last_week_str = re.sub(&amp;#039;-&amp;#039;, &amp;#039;.&amp;#039;, last_week_str)&lt;br /&gt;
            while re.search(&amp;#039;-&amp;#039;, next_week_str):&lt;br /&gt;
                next_week_str = re.sub(&amp;#039;-&amp;#039;, &amp;#039;.&amp;#039;, next_week_str)&lt;br /&gt;
&lt;br /&gt;
            # replace keywords in prefix&lt;br /&gt;
            re_this_week = re.compile(&amp;quot;THIS_WEEK&amp;quot;)&lt;br /&gt;
            re_last_week = re.compile(&amp;quot;PREV_WEEK&amp;quot;)&lt;br /&gt;
            re_next_week = re.compile(&amp;quot;NEXT_WEEK&amp;quot;)&lt;br /&gt;
            new_prefix = []&lt;br /&gt;
            for line in self.prefix:&lt;br /&gt;
                while re_this_week.search(line):&lt;br /&gt;
                    line = re_this_week.sub(this_week_str, line)&lt;br /&gt;
                while re_last_week.search(line):&lt;br /&gt;
                    line = re_last_week.sub(last_week_str, line)&lt;br /&gt;
                while re_next_week.search(line):&lt;br /&gt;
                    line = re_next_week.sub(next_week_str, line)&lt;br /&gt;
                new_prefix.append(line)&lt;br /&gt;
            self.prefix = new_prefix;&lt;br /&gt;
            # replace keywords in postfix&lt;br /&gt;
            new_postfix = []&lt;br /&gt;
            for line in self.postfix:&lt;br /&gt;
                while re_this_week.search(line):&lt;br /&gt;
                    line = re_this_week.sub(this_week_str, line)&lt;br /&gt;
                while re_last_week.search(line):&lt;br /&gt;
                    line = re_last_week.sub(last_week_str, line)&lt;br /&gt;
                while re_next_week.search(line):&lt;br /&gt;
                    line = re_next_week.sub(next_week_str, line)&lt;br /&gt;
                new_postfix.append(line)&lt;br /&gt;
            self.postfix = new_postfix;&lt;br /&gt;
        &lt;br /&gt;
    def buildColorMap(self, file):&lt;br /&gt;
        fp = open(file, &amp;#039;r&amp;#039;)&lt;br /&gt;
        line = fp.readline()&lt;br /&gt;
        while line:&lt;br /&gt;
            timestamp_result = self.re_timestamped_line.match(line)&lt;br /&gt;
            if timestamp_result:&lt;br /&gt;
                # hunt for 2-word names which we assume to be users&lt;br /&gt;
                # rather than objects&lt;br /&gt;
                name_result = self.re_two_word_name_line.match(timestamp_result.group(&amp;#039;text&amp;#039;))&lt;br /&gt;
                if name_result:&lt;br /&gt;
                    name = name_result.group(&amp;#039;username&amp;#039;)&lt;br /&gt;
                    if (name == &amp;quot;Second Life&amp;quot;):&lt;br /&gt;
                        pass&lt;br /&gt;
                    else:&lt;br /&gt;
                        self.getColor(name)&lt;br /&gt;
                else:&lt;br /&gt;
                    name_result = self.re_one_word_name_line.match(timestamp_result.group(&amp;#039;text&amp;#039;))&lt;br /&gt;
                    if name_result:&lt;br /&gt;
                        name = name_result.group(&amp;#039;username&amp;#039;)&lt;br /&gt;
                        if self.configured_color_map.has_key(name):&lt;br /&gt;
                            self.assigned_color_map[name] = self.configured_color_map[name]&lt;br /&gt;
        &lt;br /&gt;
                #    # hunt for 1-word names that match configured colors&lt;br /&gt;
            line = fp.readline()&lt;br /&gt;
        fp.close()&lt;br /&gt;
        if not self.assigned_color_map.has_key(&amp;#039;Object&amp;#039;):&lt;br /&gt;
            color = DEFAULT_OBJECT_COLOR&lt;br /&gt;
            if self.configured_color_map.has_key(&amp;#039;Object&amp;#039;):&lt;br /&gt;
                color = self.configured_color_map[&amp;#039;Object&amp;#039;]&lt;br /&gt;
            self.assigned_color_map[&amp;#039;Object&amp;#039;] = color&lt;br /&gt;
&lt;br /&gt;
    def getAssignedColorMap(self):&lt;br /&gt;
        return self.assigned_color_map&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
    def getColor(self, name):&lt;br /&gt;
        if self.assigned_color_map.has_key(name):&lt;br /&gt;
            color = self.assigned_color_map[name]&lt;br /&gt;
        else:&lt;br /&gt;
            if self.configured_color_map.has_key(name):&lt;br /&gt;
                color = self.configured_color_map[name]&lt;br /&gt;
            else:&lt;br /&gt;
                special_result = self.re_special_name.match(name)&lt;br /&gt;
                if special_result and len(self.special_colors) &amp;gt; 0:&lt;br /&gt;
                    color = self.special_colors.pop()&lt;br /&gt;
                else:&lt;br /&gt;
                    color = self.available_colors.pop()&lt;br /&gt;
            self.assigned_color_map[name] = color&lt;br /&gt;
        return color&lt;br /&gt;
        &lt;br /&gt;
&lt;br /&gt;
    def processLine(self, line):&lt;br /&gt;
        formatted_line = None&lt;br /&gt;
        line = line.rstrip()&lt;br /&gt;
&lt;br /&gt;
        timestamp_result = self.re_timestamped_line.match(line)&lt;br /&gt;
        color = None&lt;br /&gt;
        if timestamp_result:&lt;br /&gt;
            timestamp = timestamp_result.group(&amp;#039;timestamp&amp;#039;)&lt;br /&gt;
            text = timestamp_result.group(&amp;#039;text&amp;#039;)&lt;br /&gt;
            # check for two-word name&lt;br /&gt;
            resi_result = self.re_two_word_name_line.match(text)&lt;br /&gt;
            if resi_result:&lt;br /&gt;
                name = resi_result.group(&amp;#039;username&amp;#039;)&lt;br /&gt;
                if name == &amp;quot;Second Life&amp;quot; or self.deletes.has_key(name):&lt;br /&gt;
                    pass&lt;br /&gt;
                else:&lt;br /&gt;
                    color = self.getColor(name)&lt;br /&gt;
                    text = &amp;quot;: %s&amp;quot; % resi_result.group(&amp;#039;text&amp;#039;)&lt;br /&gt;
            else:&lt;br /&gt;
                # check for /me line&lt;br /&gt;
                me_result = self.re_me_line.match(text)&lt;br /&gt;
                if me_result and self.assigned_color_map.has_key(me_result.group(&amp;#039;username&amp;#039;)):&lt;br /&gt;
                    name = me_result.group(&amp;#039;username&amp;#039;)&lt;br /&gt;
                    if self.deletes.has_key(name):&lt;br /&gt;
                        pass&lt;br /&gt;
                    else:&lt;br /&gt;
                        color = self.getColor(name)&lt;br /&gt;
                        text = &amp;quot; %s&amp;quot; % me_result.group(&amp;#039;text&amp;#039;)&lt;br /&gt;
                else:&lt;br /&gt;
                    # check for object line&lt;br /&gt;
                    object_result = self.re_object_line.match(text)&lt;br /&gt;
                    if object_result:&lt;br /&gt;
                        name = object_result.group(&amp;#039;objectname&amp;#039;)&lt;br /&gt;
                        if self.deletes.has_key(name):&lt;br /&gt;
                            pass&lt;br /&gt;
                        else:&lt;br /&gt;
                            if self.assigned_color_map.has_key(name):&lt;br /&gt;
                                color = self.assigned_color_map[name]&lt;br /&gt;
                            else:&lt;br /&gt;
                                color = self.assigned_color_map[&amp;#039;Object&amp;#039;]&lt;br /&gt;
                            text = &amp;quot;: %s&amp;quot; % object_result.group(&amp;#039;text&amp;#039;)&lt;br /&gt;
            if color:&lt;br /&gt;
                self.last_color = color&lt;br /&gt;
                formatted_line = &amp;quot;%s &amp;lt;font color=%s&amp;gt;&amp;lt;b&amp;gt;%s&amp;lt;/b&amp;gt;&amp;lt;nowiki&amp;gt;%s&amp;lt;/nowiki&amp;gt;&amp;lt;/font&amp;gt;\n&amp;quot; % (timestamp, color, name, text)&lt;br /&gt;
        else:&lt;br /&gt;
            # some strange line &lt;br /&gt;
            if self.re_old_timestamp.match(line):&lt;br /&gt;
                # skip this line&lt;br /&gt;
                pass&lt;br /&gt;
            else:&lt;br /&gt;
                # assume this was continuation of the last line&lt;br /&gt;
                color = self.last_color&lt;br /&gt;
                formatted_line = &amp;quot;&amp;lt;font color=%s&amp;gt;&amp;lt;nowiki&amp;gt;%s&amp;lt;/nowiki&amp;gt;&amp;lt;/font&amp;gt;\n&amp;quot; % (color, line)&lt;br /&gt;
&lt;br /&gt;
        # check for http links&lt;br /&gt;
        if formatted_line:&lt;br /&gt;
            formatted_line = self.re_http_link.sub(replace_link, formatted_line)&lt;br /&gt;
    &lt;br /&gt;
            # check for useless &amp;lt;nowiki&amp;gt;&amp;lt;/nowiki&amp;gt; pairs&lt;br /&gt;
            formatted_line = self.re_useless_nowiki.sub(&amp;quot;&amp;quot;, formatted_line)&lt;br /&gt;
&lt;br /&gt;
        return formatted_line&lt;br /&gt;
&lt;br /&gt;
def main():&lt;br /&gt;
    parser = optparse.OptionParser(&amp;#039;%prog [options] chatlog.txt\n\tby default automatically stores result in chatlog.wiki&amp;#039;)&lt;br /&gt;
    parser.add_option( &amp;#039;-s&amp;#039;, &amp;#039;--stdout&amp;#039;,&lt;br /&gt;
        action=&amp;#039;store_true&amp;#039;, dest=&amp;#039;stdout&amp;#039;, &lt;br /&gt;
        default=False,&lt;br /&gt;
        help=&amp;#039;print to stdout rather than default output file&amp;#039;,)&lt;br /&gt;
    parser.add_option( &amp;#039;-o&amp;#039;, &amp;#039;--output-file&amp;#039;,&lt;br /&gt;
        type=&amp;#039;string&amp;#039;, dest=&amp;#039;output_file&amp;#039;,&lt;br /&gt;
        default=None,&lt;br /&gt;
        help=&amp;#039;write to OUTPUT_FILE&amp;#039;)&lt;br /&gt;
    parser.add_option( &amp;#039;-c&amp;#039;, &amp;#039;--config-file&amp;#039;,&lt;br /&gt;
        type=&amp;#039;string&amp;#039;, dest=&amp;#039;config_file&amp;#039;,&lt;br /&gt;
        default=None,&lt;br /&gt;
        help=&amp;#039;resource config file (default is ~/.chatwickrc)&amp;#039;)&lt;br /&gt;
    parser.add_option( &amp;#039;-v&amp;#039;, &amp;#039;--verbose&amp;#039;,&lt;br /&gt;
        action=&amp;#039;store_true&amp;#039;, dest=&amp;#039;verbose&amp;#039;, &lt;br /&gt;
        default=False,&lt;br /&gt;
        help=&amp;#039;print verbose output to stdout&amp;#039;,)&lt;br /&gt;
    parser.add_option( &amp;#039;-l&amp;#039;, &amp;#039;--list-colors&amp;#039;,&lt;br /&gt;
        action=&amp;#039;store_true&amp;#039;, dest=&amp;#039;list_colors&amp;#039;, &lt;br /&gt;
        default=False,&lt;br /&gt;
        help=&amp;#039;list colors available and exit&amp;#039;,)&lt;br /&gt;
    parser.add_option( &amp;#039;-T&amp;#039;, &amp;#039;--test-only&amp;#039;,&lt;br /&gt;
        action=&amp;#039;store_true&amp;#039;, dest=&amp;#039;test_only&amp;#039;, &lt;br /&gt;
        default=False,&lt;br /&gt;
        help=&amp;#039;set --verbose and do not write output&amp;#039;,)&lt;br /&gt;
    options, args = parser.parse_args()&lt;br /&gt;
&lt;br /&gt;
    reader = LineProcessor()&lt;br /&gt;
&lt;br /&gt;
    if options.list_colors:&lt;br /&gt;
        reader.listColors()&lt;br /&gt;
        sys.exit()&lt;br /&gt;
&lt;br /&gt;
    # make sure input file is specified and valid&lt;br /&gt;
    if len(args) == 0:&lt;br /&gt;
        print &amp;quot;No input file was specified.&amp;quot;&lt;br /&gt;
        parser.print_help()&lt;br /&gt;
        sys.exit()&lt;br /&gt;
&lt;br /&gt;
    input_file = args[0]&lt;br /&gt;
&lt;br /&gt;
    if True == options.test_only:&lt;br /&gt;
        options.verbose = True&lt;br /&gt;
    if not os.path.isfile(input_file):&lt;br /&gt;
        raise &amp;quot;Could not find input file &amp;#039;%s&amp;#039;&amp;quot; % input_file&lt;br /&gt;
    if input_file == options.output_file:&lt;br /&gt;
        raise &amp;quot;Input and output are the same file&amp;quot;&lt;br /&gt;
    if options.verbose:&lt;br /&gt;
        print &amp;quot;input_file = &amp;#039;%s&amp;#039;&amp;quot; % input_file&lt;br /&gt;
&lt;br /&gt;
    if None == options.config_file:&lt;br /&gt;
        home_dir = os.environ.get(&amp;quot;HOME&amp;quot;)&lt;br /&gt;
        options.config_file = os.path.join(home_dir, &amp;quot;.chatwickrc&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    # read config file&lt;br /&gt;
    if os.path.isfile(options.config_file):&lt;br /&gt;
        reader.readConfig(options.config_file)&lt;br /&gt;
        reader.replaceKeywordsInConfig(input_file)&lt;br /&gt;
            &lt;br /&gt;
&lt;br /&gt;
    # open the output file&lt;br /&gt;
    output_fp = None&lt;br /&gt;
    if options.stdout:&lt;br /&gt;
        if not options.test_only:&lt;br /&gt;
            output_fp = sys.stdout&lt;br /&gt;
    else:&lt;br /&gt;
        if None != options.output_file:&lt;br /&gt;
            if not options.test_only:&lt;br /&gt;
                output_fp = open(options.output_file, &amp;#039;w&amp;#039;)&lt;br /&gt;
            if options.verbose:&lt;br /&gt;
                print &amp;quot;auto output_file = &amp;#039;%s&amp;#039;&amp;quot; % options.output_file&lt;br /&gt;
        elif options.stdout:&lt;br /&gt;
            if not options.test_only:&lt;br /&gt;
                output_fp = sys.stdout&lt;br /&gt;
        else:&lt;br /&gt;
            (file_name, ext) = os.path.splitext(input_file)&lt;br /&gt;
            if ext == &amp;quot;.wiki&amp;quot;:&lt;br /&gt;
                raise &amp;quot;Will not auto overwrite filename with &amp;#039;.wiki&amp;#039; extension.&amp;quot;&lt;br /&gt;
            options.output_file = file_name + &amp;quot;.wiki&amp;quot;&lt;br /&gt;
            if not options.test_only:&lt;br /&gt;
                output_fp = open(options.output_file, &amp;#039;w&amp;#039;)&lt;br /&gt;
            if options.verbose:&lt;br /&gt;
                print &amp;quot;output_file = &amp;#039;%s&amp;#039;&amp;quot; % options.output_file&lt;br /&gt;
        &lt;br /&gt;
    reader.buildColorMap(input_file)&lt;br /&gt;
&lt;br /&gt;
    input_fp = open(input_file, &amp;#039;r&amp;#039;)&lt;br /&gt;
&lt;br /&gt;
    color_map = reader.getAssignedColorMap()&lt;br /&gt;
    if color_map.has_key(&amp;#039;Objects&amp;#039;):&lt;br /&gt;
        del color_map[&amp;#039;Object&amp;#039;]&lt;br /&gt;
&lt;br /&gt;
    speaker_count = len(color_map)&lt;br /&gt;
    column_count = int(speaker_count/5)&lt;br /&gt;
    if column_count &amp;gt; 3:&lt;br /&gt;
        column_count = 3&lt;br /&gt;
&lt;br /&gt;
    prefix = reader.prefix&lt;br /&gt;
    for line in prefix:&lt;br /&gt;
        write_output(output_fp, line)&lt;br /&gt;
        write_output(output_fp, &amp;quot;\n&amp;quot;)&lt;br /&gt;
    write_output(output_fp, &amp;quot;== List of Speakers ==\n&amp;quot;)&lt;br /&gt;
    write_output(output_fp, TABLE_START)&lt;br /&gt;
    column_index = 1&lt;br /&gt;
&lt;br /&gt;
    # generate an alphabetized map of names&lt;br /&gt;
    names = color_map.keys()&lt;br /&gt;
    lname_map = {}&lt;br /&gt;
    for name in names:&lt;br /&gt;
        lname = name.lower()&lt;br /&gt;
        lname_map[lname] = name&lt;br /&gt;
    lnames = lname_map.keys()&lt;br /&gt;
    lnames.sort()&lt;br /&gt;
&lt;br /&gt;
    # list each name in its color&lt;br /&gt;
    for lname in lnames:&lt;br /&gt;
        name = lname_map[lname]&lt;br /&gt;
        color = color_map[name]&lt;br /&gt;
        if name == &amp;quot;Object&amp;quot;:&lt;br /&gt;
            continue&lt;br /&gt;
        line = &amp;quot;%s&amp;lt;font color=%s&amp;gt;&amp;lt;b&amp;gt;%s&amp;lt;/b&amp;gt;&amp;lt;/font&amp;gt;\n&amp;quot; % (TABLE_COLUMN_START, color, name)&lt;br /&gt;
        write_output(output_fp, line)&lt;br /&gt;
        if column_index == column_count:&lt;br /&gt;
            write_output(output_fp, TABLE_ROW_END)&lt;br /&gt;
            column_index = 1&lt;br /&gt;
        else:&lt;br /&gt;
            column_index += 1&lt;br /&gt;
    write_output(output_fp, TABLE_END)    &lt;br /&gt;
&lt;br /&gt;
    write_output(output_fp, &amp;quot;\n&amp;quot;)&lt;br /&gt;
    write_output(output_fp, &amp;quot;== Transcript ==\n&amp;quot;)&lt;br /&gt;
    line = input_fp.readline();&lt;br /&gt;
    while line:&lt;br /&gt;
        new_line = reader.processLine(line)&lt;br /&gt;
        if new_line and output_fp:&lt;br /&gt;
            write_output(output_fp, new_line) &lt;br /&gt;
            write_output(output_fp, &amp;quot;\n&amp;quot;)&lt;br /&gt;
        line = input_fp.readline();&lt;br /&gt;
    input_fp.close()&lt;br /&gt;
&lt;br /&gt;
    postfix = reader.postfix&lt;br /&gt;
&lt;br /&gt;
    for line in postfix:&lt;br /&gt;
        write_output(output_fp, line)&lt;br /&gt;
        write_output(output_fp, &amp;quot;\n&amp;quot;)&lt;br /&gt;
    output_fp.close()&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;#039;__main__&amp;#039;:&lt;br /&gt;
    sys.exit(main())&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Andrew Linden</name></author>
	</entry>
</feed>