mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-10-17 18:37:14 +00:00
add explicit IRC-aware comparison keys
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
# Explicit IRC comparison keys
|
||||||
|
|
||||||
|
`IRCName(text, context=None, casemapping=None, connection_id=None)` is an
|
||||||
|
immutable helper, not a str subclass. Construct it explicitly for dictionary/set
|
||||||
|
keys; existing get_info/list/event values remain normal strings.
|
||||||
|
Equality and hashing include the connection ID and captured casemapping, so
|
||||||
|
identical-looking names on different connections are different keys. An IRCName
|
||||||
|
never compares equal to a plain str. `str(key)` / `key.text` recover the spelling.
|
||||||
|
|
||||||
|
`irc_casefold(text, casemapping='rfc1459')` supports ascii, strict-rfc1459 and
|
||||||
|
rfc1459. Only ASCII IRC characters fold; Unicode casefold would be incorrect.
|
||||||
|
The new `get_info('casemapping')` reports the comparator actually used by core.
|
||||||
|
Current master handles ASCII and RFC1459; this patch does not change its handling
|
||||||
|
of advertised strict-rfc1459. An explicit mapping is available for scripts that
|
||||||
|
need strict-rfc1459 independently. Keys capture their mapping so hashes stay
|
||||||
|
stable; rebuild a collection if the connection's mapping changes.
|
||||||
|
|
||||||
|
```python
|
||||||
|
seen = {zoitechat.IRCName('[SomeNick]')}
|
||||||
|
assert zoitechat.IRCName('{somenick}') in seen # RFC1459 connection
|
||||||
|
```
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
"""Explicit immutable IRC names; existing string-returning APIs are unchanged."""
|
||||||
|
import _zoitechat as api
|
||||||
|
|
||||||
|
__all__ = ['IRCName', 'irc_casefold']
|
||||||
|
|
||||||
|
|
||||||
|
def irc_casefold(text, casemapping='rfc1459'):
|
||||||
|
"""Fold ASCII IRC characters only; never apply Unicode lower()/casefold()."""
|
||||||
|
if casemapping not in ('ascii', 'strict-rfc1459', 'rfc1459'):
|
||||||
|
raise ValueError('unsupported IRC casemapping')
|
||||||
|
extra = {'[': '{', ']': '}', '\\': '|'} if casemapping != 'ascii' else {}
|
||||||
|
if casemapping == 'rfc1459':
|
||||||
|
extra['^'] = '~'
|
||||||
|
return ''.join(chr(ord(c) + 32) if 'A' <= c <= 'Z' else extra.get(c, c)
|
||||||
|
for c in text)
|
||||||
|
|
||||||
|
|
||||||
|
class IRCName:
|
||||||
|
"""Equality/hash include connection identity and the captured casemapping."""
|
||||||
|
__slots__ = ('_text', '_casemapping', '_connection_id', '_key')
|
||||||
|
|
||||||
|
def __init__(self, text, context=None, casemapping=None, connection_id=None):
|
||||||
|
context = context or api.get_context()
|
||||||
|
if casemapping is None:
|
||||||
|
casemapping = context.get_info('casemapping')
|
||||||
|
if casemapping is None:
|
||||||
|
raise RuntimeError('host does not expose its comparison casemapping')
|
||||||
|
if connection_id is None:
|
||||||
|
previous = api.get_context()
|
||||||
|
if not context.set():
|
||||||
|
raise ValueError('context has closed')
|
||||||
|
try:
|
||||||
|
connection_id = api.get_prefs('id')
|
||||||
|
finally:
|
||||||
|
previous.set()
|
||||||
|
object.__setattr__(self, '_text', text)
|
||||||
|
object.__setattr__(self, '_casemapping', casemapping)
|
||||||
|
object.__setattr__(self, '_connection_id', connection_id)
|
||||||
|
object.__setattr__(self, '_key', (connection_id, casemapping,
|
||||||
|
irc_casefold(text, casemapping)))
|
||||||
|
|
||||||
|
def __setattr__(self, name, value):
|
||||||
|
raise AttributeError('IRCName is immutable')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def text(self):
|
||||||
|
return self._text
|
||||||
|
|
||||||
|
@property
|
||||||
|
def casemapping(self):
|
||||||
|
return self._casemapping
|
||||||
|
|
||||||
|
@property
|
||||||
|
def connection_id(self):
|
||||||
|
return self._connection_id
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self._text
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'IRCName({!r}, casemapping={!r}, connection_id={!r})'.format(
|
||||||
|
self._text, self._casemapping, self._connection_id)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, IRCName):
|
||||||
|
return NotImplemented
|
||||||
|
return self._key == other._key
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
result = self.__eq__(other)
|
||||||
|
return NotImplemented if result is NotImplemented else not result
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self._key)
|
||||||
@@ -1187,6 +1187,16 @@ zoitechat_get_info (zoitechat_plugin *ph, const char *id)
|
|||||||
return text_find_format_string (e);
|
return text_find_format_string (e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Report the comparison actually used by this connection, not an
|
||||||
|
* advertised mapping that the current core does not implement. */
|
||||||
|
if (strcmp (id, "casemapping") == 0)
|
||||||
|
{
|
||||||
|
if (!is_session (ph->context))
|
||||||
|
return NULL;
|
||||||
|
return ph->context->server->p_cmp == g_ascii_strcasecmp
|
||||||
|
? "ascii" : "rfc1459";
|
||||||
|
}
|
||||||
|
|
||||||
hash = str_hash (id);
|
hash = str_hash (id);
|
||||||
/* do the session independant ones first */
|
/* do the session independant ones first */
|
||||||
switch (hash)
|
switch (hash)
|
||||||
|
|||||||
Reference in New Issue
Block a user