99
1010from irclib .errors import ParseError
1111
12+ __all__ = (
13+ 'Cap' , 'CapList' , 'MessageTag' , 'TagList' , 'Prefix' , 'ParamList' , 'Message'
14+ )
15+
1216TAGS_SENTINEL = '@'
1317TAGS_SEP = ';'
1418TAG_VALUE_SEP = '='
@@ -155,7 +159,15 @@ def parse(text):
155159 # Some networks (ie: freenode) send a trailing space in a CAP ACK
156160 stripped = text .strip ()
157161
158- return CapList (map (Cap .parse , stripped .split (CAP_SEP )))
162+ if not text :
163+ caps = []
164+ else :
165+ caps = (
166+ Cap .parse (s )
167+ for s in stripped .split (CAP_SEP )
168+ )
169+
170+ return CapList (caps )
159171
160172
161173class MessageTag (Parseable ):
@@ -251,29 +263,44 @@ def parse(text):
251263class TagList (Parseable , dict ):
252264 """Object representing the list of message tags on a line"""
253265
254- def __init__ (self , tags ):
266+ def __init__ (self , tags = () ):
255267 super ().__init__ ((tag .name , tag ) for tag in tags )
256268
257269 def __str__ (self ):
258270 return TAGS_SEP .join (map (str , self .values ()))
259271
260- def __eq__ (self , other ):
261- if isinstance (other , str ):
262- return self == TagList .parse (other )
272+ @staticmethod
273+ def _cmp_type_map (obj ):
274+ if isinstance (obj , str ):
275+ return TagList .parse (obj )
276+
277+ if isinstance (obj , dict ):
278+ sample = next (iter (obj .values ()), None )
279+ if obj and (sample is None or isinstance (sample , str )):
280+ # Handle str -> str dict
281+ return TagList .from_dict (obj )
263282
264- if isinstance (other , dict ):
265- return dict (self ) == dict (other )
283+ # Handle str -> MessageTag dict
284+ return dict (obj )
285+
286+ if isinstance (obj , list ):
287+ return TagList (obj )
266288
267289 return NotImplemented
268290
269- def __ne__ (self , other ):
270- if isinstance (other , str ):
271- return self != TagList .parse (other )
291+ def __eq__ (self , other ):
292+ obj = self ._cmp_type_map (other )
293+ if obj is NotImplemented :
294+ return NotImplemented
272295
273- if isinstance (other , dict ):
274- return dict (self ) != dict (other )
296+ return dict (self ) == dict (obj )
275297
276- return NotImplemented
298+ def __ne__ (self , other ):
299+ obj = self ._cmp_type_map (other )
300+ if obj is NotImplemented :
301+ return NotImplemented
302+
303+ return dict (self ) != dict (obj )
277304
278305 @staticmethod
279306 def parse (text ):
@@ -299,7 +326,7 @@ class Prefix(Parseable):
299326 Object representing the prefix of a line
300327 """
301328
302- def __init__ (self , nick , user = None , host = None ):
329+ def __init__ (self , nick = None , user = None , host = None ):
303330 self ._nick = nick or ''
304331 self ._user = user or ''
305332 self ._host = host or ''
@@ -345,7 +372,7 @@ def __str__(self):
345372 return self .mask
346373
347374 def __bool__ (self ):
348- return bool (self . nick )
375+ return any (self )
349376
350377 def __eq__ (self , other ):
351378 if isinstance (other , str ):
@@ -374,10 +401,11 @@ def parse(text):
374401 :return: Parsed Object
375402 """
376403 if not text :
377- return Prefix ('' )
404+ return Prefix ()
378405
379406 match = PREFIX_RE .match (text )
380- if not match :
407+ if not match : # pragma: no cover
408+ # This should never trip, we are pretty lenient with prefixes
381409 raise ParseError ("Invalid IRC prefix format" )
382410
383411 nick , user , host = match .groups ()
@@ -401,7 +429,9 @@ def __str__(self):
401429 if not self :
402430 return ''
403431
404- if self .has_trail or PARAM_SEP in self [- 1 ]:
432+ needs_trail = PARAM_SEP in self [- 1 ] or self [- 1 ].startswith (TRAIL_SENTINEL ) or not self [- 1 ]
433+
434+ if self .has_trail or needs_trail :
405435 return PARAM_SEP .join (self [:- 1 ] + [TRAIL_SENTINEL + self [- 1 ]])
406436
407437 return PARAM_SEP .join (self )
0 commit comments