Exemple #1
0
class Line:
    """
    Low-level representation of an IRC message, either sent or received.  Calling :func:`str` on a
    line will encode it suitable for transmission.

    Attributes:
        command (str):
            IRC verb or numeric.
        args (str list):
            Additional arguments for this message.
        source (str list):
            Optional source of this message.
        tags ((str, str) dict):
            Any tags attached to the message.
    """

    _format = re.compile("(?:@(?P<tags>[a-z0-9-]+(?:=[^; ]+)?(?:;[a-z0-9-]+(?:=[^; ]+)?)*) +)?"
                         "(?::(?P<source>[^ ]+) +)?(?P<command>[a-z]+|[0-9]{3})"
                         "(?P<args>(?: +[^: ][^ ]*)*)(?: +:(?P<trailing>.*))?", re.I)

    def __init__(self, command, *args, source=None, tags=None):
        self.command = command.upper()
        self.args = args
        self.source = source
        self.tags = tags

    # Conveniently, this generates timestamp identifiers of the desired format.
    next_ts = immp.IDGen()

    @classmethod
    def parse(cls, line):
        """
        Take a raw IRC line and decode it into a :class:`.Line`.

        Args:
            line (str):
                Raw IRC message.

        Returns:
            .Line:
                Parsed line.
        """
        match = cls._format.match(line)
        if not match:
            raise ValueError("Invalid line: '{}'".format(line))
        tagpart, source, command, argpart, trailing = match.groups()
        tags = {}
        args = []
        if tagpart:
            for item in tagpart.split(";"):
                key, *val = item.split("=", 1)
                tags[key] = val[0] if val else True
        if argpart:
            args = argpart.split()
        if trailing:
            args.append(trailing)
        return cls(command, *args, source=source, tags=tags)

    def __str__(self):
        line = self.command
        if self.source:
            line = ":{} {}".format(self.source, line)
        if self.tags:
            tagpart = []
            for key, value in self.tags:
                tagpart.append(key if value is True else "{}={}".format(key, value))
            line = "@{} {}".format(";".join(tagpart), line)
        if self.args:
            line = " ".join([line, *self.args[:-1], ":{}".format(self.args[-1])])
        return line

    def __repr__(self):
        return "<{}: {}{}{}>".format(self.__class__.__name__, self.command,
                                     " @ {}".format(repr(self.source)) if self.source else "",
                                     " {}".format(repr(list(self.args))) if self.args else "")
Exemple #2
0
 def __init__(self, name, config, host):
     super().__init__(name, config, host)
     self.counter = immp.IDGen()
     self.user = immp.User(id_="dummy", real_name=name)
     self.channel = immp.Channel(self, "dummy")
     self._task = None
Exemple #3
0
class SyncRef:
    """
    Representation of a single synced message.

    Attributes:
        key (str):
            Unique synced message identifier, used by :class:`.SyncPlug` when yielding messages.
        ids ((.Channel, str) dict):
            Mapping from :class:`.Channel` to a list of echoed message IDs.
        revisions ((.Channel, (str, set) dict) dict):
            Mapping from :class:`.Channel` to message ID to synced revisions of that message.
        source (.Message):
            Original copy of the source message, if we have it.
    """

    next_key = immp.IDGen()

    @classmethod
    def from_backref_map(cls, key, mapped, host):
        """
        Take a mapping generated in :meth:`.SyncBackRef.map_from_key` and produce a local reference
        suitable for the memory cache.

        Args:
            key (str):
                Synced message identifier.
            mapped (((str, str), .SyncBackRef list) dict):
                Generated reference mapping.
            host (.Host):
                Parent host instance, needed to resolve network IDs to plugs.

        Returns:
            .SyncRef:
                Newly created reference.
        """
        ids = {}
        for (network, source), synced in mapped.items():
            for plug in host.plugs.values():
                if plug.network_id == network:
                    ids[immp.Channel(plug, source)] = [backref.message for backref in synced]
        return cls(ids, key=key)

    def __init__(self, ids, *, key=None, source=None, origin=None):
        self.key = key or self.next_key()
        self.ids = defaultdict(list, ids)
        self.revisions = defaultdict(lambda: defaultdict(set))
        self.source = source
        if origin:
            self.ids[origin.channel].append(origin.id)
            self.revision(origin)

    def revision(self, sent):
        """
        Log a new revision of a message.

        Args:
            sent (.Receipt):
                Updated message relating to a previously synced message.

        Returns:
            bool:
                ``True`` if this is an edit (i.e. we've already seen a base revision for this
                message) and needs syncing to other channels.
        """
        self.revisions[sent.channel][sent.id].add(sent.revision)
        return len(self.revisions[sent.channel][sent.id]) > 1

    def __repr__(self):
        return "<{}: #{} x{}{}>".format(self.__class__.__name__, self.key, len(self.ids),
                                        " {}".format(repr(self.source)) if self.source else "")