Skip to content
Snippets Groups Projects
sed.py 5.28 KiB
# sed module
# <Kays> test
# <Kays> s/^t/b/g
# <guppy> <Kays> best
# --Kays
import re
import sys

@plugin
class sed(object):
    def __init__(self, server):
        self.server = server
        self.prnt = server.prnt
        server.handle("message", self.handle_message)

        # this regex determines if the message appears
        # to possible be a sed request
        self.message_cond = re.compile('^s(.)')

        # this regex determines if the sed flags are valid
        self.valid_flags = re.compile('^[ig]+$')

        self.backlog = { }

        # this is the threshold for how many messages
        # that backlog should keep track of for any one
        # channel. If the user decides to run a sed on
        # a message that is 10 lines before it, then
        # it's not a very useful sed to say the least
        self.backlog_threshold = 10

    def handle_message(self, channel, user, message):
        # if there is no backlog for this channel,
        # then just store the message and exit
        if self.backlog.get(channel, None) is None:
            self.backlog_message(channel, user, message)
            return

        match = self.message_cond.findall(message)
        mlen = len(match)

        # mlen == 0 (no match case)
        # backlog the message and exit
        if mlen == 0:
            self.backlog_message(channel, user, message)
            return

        # otherwise, there's a match.
        # sed delimiter
        delimiter = match[0]

        # split the string into single characters
        message_chars = list(message)

        # a sed command has at least 3 parts,
        # and at most, 4
        parts = []

        # escape sequences needs to be ignored
        run_next = False

        # for each message_chars, scrob all parts
        for index in range(0, len(message_chars)):
            # if this char is escaped, ignore
            if run_next:
                run_next = False
                continue

            # if this is an escape char
            if message_chars[index] == '\\':
                run_next = True
                continue

            # if this char is a delimiter, save it
            if message_chars[index] == delimiter:
                parts.append(index)

        # verify that we have all relevant parts
        # if this doesn't validate, then append
        # the message onto the backlog and exit
        if len(parts) != 3:
            # backlog the message
            self.backlog_message(channel, user, message)

            # now exit
            return

        # at this point, sed is good, so let's grab all 3-4
        # parts of sed and make our replacements
        # PART 1: s/(..)//
        sed_part1 = message[parts[0]+1:parts[1]]

        # PART 2: s//(..)/
        sed_part2 = message[parts[1]+1:parts[2]]

        # PART 3: s///(..)
        sed_part3 = message[parts[2]+1:]

        # validate that the flags are decent
        # if not, just add the message and exit
        if len(sed_part3) > 0 and not self.valid_flags.match(sed_part3):
            # backlog the message
            self.backlog_message(channel, user, message)

            # exit
            return

        # get the message to run sed with
        repl_message = self.find_backlog_message(channel, user, sed_part1, sed_part3)

        # if repl_message is None, log the message and exit
        if repl_message is None:
            self.backlog_message(channel, user, message)
            return

        # run sed!
        # if global replace
        flag_global = 1
        if 'g' in sed_part3:
            flag_global = 0

        # WARNING: re.sub does not have a flags
        # parameter for Python version < 2.7
        # for Python < 2.7, flag 'i' will be ignored
        if sys.version_info[:2] == (2,7):
            # if ignore case flag is set
            flag_ignore = 0
            if 'i' in sed_part3:
                flag_ignore = re.I

            result = re.sub(sed_part1, sed_part2, repl_message, count=flag_global, flags=flag_ignore)
        else:
            result = re.sub(sed_part1, sed_part2, repl_message, count=flag_global)

        # add the sed result to the backlog
        self.backlog_message(channel, user, result)

        # print the message
        self.server.doMessage(channel, "<%s> %s" % (user, result))

    def backlog_message (self, channel, user, message):
        # if the backlog doesn't contain this
        # channel, add it
        if self.backlog.get(channel, None) is None:
            self.backlog[channel] = []

        # if the backlog is over the threshold, drop one
        if len(self.backlog[channel]) > self.backlog_threshold:
            self.backlog[channel].pop(0)

        # record the message
        self.backlog[channel].append([user, message])

    def find_backlog_message (self, channel, user, regex, regex_modifiers):
        # if backlog channel doesn't exist, return nothing
        if self.backlog.get(channel, None) is None:
            return

        modifiers = 0
        # don't match 'i' (ignore-case) for Python versions < 2.7
        if sys.version_info[:2] == (2,7) and 'i' in regex_modifiers:
            modifiers = re.I

        reg_obj = re.compile(regex, modifiers)
        for usr, msg in reversed(self.backlog[channel]):
            if user == usr and len(reg_obj.findall(msg)) > 0:
                return msg

        return