twisted.protocols.irc

Package twisted :: Package protocols :: Module irc |
[frames | no frames]

## Module twisted.protocols.irc

Internet Relay Chat Protocol for client and server.

Stability: semi-stable.

# Future Plans

The way the IRCClient class works here encourages people to implement IRC clients by subclassing the ephemeral protocol class, and it tends to end up with way more state than it should for an object which will be destroyed as soon as the TCP transport drops. Someone oughta do something about that, ya know?

The DCC support needs to have more hooks for the client for it to be able to ask the user things like "Do you want to accept this session?" and "Transfer #2 is 67% done." and otherwise manage the DCC sessions.

Test coverage needs to be better.

Author: Kevin Turner

See Also:
  RFC 1459: Internet Relay Chat Protocol, RFC 2812: Internet Relay Chat: Client Protocol, The Client-To-Client-Protocol

Classes
`DccChat` | Direct Client Connection protocol type CHAT.
`DccChatFactory` |
`DccFileReceive` | Higher-level coverage for getting a file from DCC SEND.
`DccFileReceiveBasic` | Bare protocol to receive a Direct Client Connection SEND stream.
`DccSendFactory` |
`DccSendProtocol` | Protocol for an outgoing Direct Client Connection SEND.
`IRC` | Internet Relay Chat server protocol.
`IRCClient` | Internet Relay Chat client protocol, with sprinkles.

Exceptions
`IRCBadMessage` |
`IRCPasswordMismatch` |

Function Summary
| `ctcpDequote(s)`
| ``ctcpExtract`(message)` Extract CTCP data from a string.
| `ctcpQuote(s)`
| ``ctcpStringify`(messages)` Return string
| ``dccDescribe`(data)` Given the data chunk from a DCC query, return a descriptive string.
| `dccParseAddress(address)`
| ``fileSize`(file)` I'll try my damndest to determine the size of this file object.
| `lowDequote(s)`
| `lowQuote(s)`
| ``parsemsg`(s)` Breaks a message from an IRC server into its prefix, command, and arguments.
| ``split`(str, length)` I break a message into multiple lines.

Variable Summary
`str` | `__version__` = `'1.94'`
`str` | `CHANNEL_PREFIXES` = `'&#!+'`
`str` | `CR` = `'\r'`
`str` | `ERR_ALREADYREGISTRED` = `'462'`
`str` | `ERR_BADCHANMASK` = `'476'`
`str` | `ERR_BADCHANNELKEY` = `'475'`
`str` | `ERR_BADMASK` = `'415'`
`str` | `ERR_BANLISTFULL` = `'478'`
`str` | `ERR_BANNEDFROMCHAN` = `'474'`
`str` | `ERR_CANNOTSENDTOCHAN` = `'404'`
`str` | `ERR_CANTKILLSERVER` = `'483'`
`str` | `ERR_CHANNELISFULL` = `'471'`
`str` | `ERR_CHANOPRIVSNEEDED` = `'482'`
`str` | `ERR_ERRONEUSNICKNAME` = `'432'`
`str` | `ERR_FILEERROR` = `'424'`
`str` | `ERR_INVITEONLYCHAN` = `'473'`
`str` | `ERR_KEYSET` = `'467'`
`str` | `ERR_NEEDMOREPARAMS` = `'461'`
`str` | `ERR_NICKCOLLISION` = `'436'`
`str` | `ERR_NICKNAMEINUSE` = `'433'`
`str` | `ERR_NOADMININFO` = `'423'`
`str` | `ERR_NOCHANMODES` = `'477'`
`str` | `ERR_NOLOGIN` = `'444'`
`str` | `ERR_NOMOTD` = `'422'`
`str` | `ERR_NONICKNAMEGIVEN` = `'431'`
`str` | `ERR_NOOPERHOST` = `'491'`
`str` | `ERR_NOORIGIN` = `'409'`
`str` | `ERR_NOPERMFORHOST` = `'463'`
`str` | `ERR_NOPRIVILEGES` = `'481'`
`str` | `ERR_NORECIPIENT` = `'411'`
`str` | `ERR_NOSERVICEHOST` = `'492'`
`str` | `ERR_NOSUCHCHANNEL` = `'403'`
`str` | `ERR_NOSUCHNICK` = `'401'`
`str` | `ERR_NOSUCHSERVER` = `'402'`
`str` | `ERR_NOSUCHSERVICE` = `'408'`
`str` | `ERR_NOTEXTTOSEND` = `'412'`
`str` | `ERR_NOTONCHANNEL` = `'442'`
`str` | `ERR_NOTOPLEVEL` = `'413'`
`str` | `ERR_NOTREGISTERED` = `'451'`
`str` | `ERR_PASSWDMISMATCH` = `'464'`
`str` | `ERR_RESTRICTED` = `'484'`
`str` | `ERR_SUMMONDISABLED` = `'445'`
`str` | `ERR_TOOMANYCHANNELS` = `'405'`
`str` | `ERR_TOOMANYTARGETS` = `'407'`
`str` | `ERR_UMODEUNKNOWNFLAG` = `'501'`
`str` | `ERR_UNAVAILRESOURCE` = `'437'`
`str` | `ERR_UNIQOPPRIVSNEEDED` = `'485'`
`str` | `ERR_UNKNOWNCOMMAND` = `'421'`
`str` | `ERR_UNKNOWNMODE` = `'472'`
`str` | `ERR_USERNOTINCHANNEL` = `'441'`
`str` | `ERR_USERONCHANNEL` = `'443'`
`str` | `ERR_USERSDISABLED` = `'446'`
`str` | `ERR_USERSDONTMATCH` = `'502'`
`str` | `ERR_WASNOSUCHNICK` = `'406'`
`str` | `ERR_WILDTOPLEVEL` = `'414'`
`str` | `ERR_YOUREBANNEDCREEP` = `'465'`
`str` | `ERR_YOUWILLBEBANNED` = `'466'`
`str` | `LF` = `'\n'`
`str` | `M_QUOTE` = `'\x10'`
`dict` | `mDequoteTable` = `{'0': '\x00', 'r': '\r', '\x10': '\x10',...`
`SRE_Pattern` | `mEscape_re` = `.`
`dict` | `mQuoteTable` = `{'\x00': '\x100', '\n': '\x10n', '\r': '\x...`
`str` | `NL` = `'\n'`
`str` | `NUL` = `'\x00'`
`dict` | `numeric_to_symbolic` = `{'347': 'RPL_ENDOFINVITELIST', '21...`
`str` | `RPL_ADMINEMAIL` = `'259'`
`str` | `RPL_ADMINLOC` = `'258'`
`str` | `RPL_ADMINME` = `'256'`
`str` | `RPL_AWAY` = `'301'`
`str` | `RPL_BANLIST` = `'367'`
`str` | `RPL_BOUNCE` = `'005'`
`str` | `RPL_CHANNELMODEIS` = `'324'`
`str` | `RPL_CREATED` = `'003'`
`str` | `RPL_ENDOFBANLIST` = `'368'`
`str` | `RPL_ENDOFEXCEPTLIST` = `'349'`
`str` | `RPL_ENDOFINFO` = `'374'`
`str` | `RPL_ENDOFINVITELIST` = `'347'`
`str` | `RPL_ENDOFLINKS` = `'365'`
`str` | `RPL_ENDOFMOTD` = `'376'`
`str` | `RPL_ENDOFNAMES` = `'366'`
`str` | `RPL_ENDOFSTATS` = `'219'`
`str` | `RPL_ENDOFUSERS` = `'394'`
`str` | `RPL_ENDOFWHO` = `'315'`
`str` | `RPL_ENDOFWHOIS` = `'318'`
`str` | `RPL_ENDOFWHOWAS` = `'369'`
`str` | `RPL_EXCEPTLIST` = `'348'`
`str` | `RPL_INFO` = `'371'`
`str` | `RPL_INVITELIST` = `'346'`
`str` | `RPL_INVITING` = `'341'`
`str` | `RPL_ISON` = `'303'`
`str` | `RPL_LINKS` = `'364'`
`str` | `RPL_LIST` = `'322'`
`str` | `RPL_LISTEND` = `'323'`
`str` | `RPL_LISTSTART` = `'321'`
`str` | `RPL_LUSERCHANNELS` = `'254'`
`str` | `RPL_LUSERCLIENT` = `'251'`
`str` | `RPL_LUSERME` = `'255'`
`str` | `RPL_LUSEROP` = `'252'`
`str` | `RPL_LUSERUNKNOWN` = `'253'`
`str` | `RPL_MOTD` = `'372'`
`str` | `RPL_MOTDSTART` = `'375'`
`str` | `RPL_MYINFO` = `'004'`
`str` | `RPL_NAMREPLY` = `'353'`
`str` | `RPL_NOTOPIC` = `'331'`
`str` | `RPL_NOUSERS` = `'395'`
`str` | `RPL_NOWAWAY` = `'306'`
`str` | `RPL_REHASHING` = `'382'`
`str` | `RPL_SERVLIST` = `'234'`
`str` | `RPL_SERVLISTEND` = `'235'`
`str` | `RPL_STATSCOMMANDS` = `'212'`
`str` | `RPL_STATSLINKINFO` = `'211'`
`str` | `RPL_STATSOLINE` = `'243'`
`str` | `RPL_STATSUPTIME` = `'242'`
`str` | `RPL_SUMMONING` = `'342'`
`str` | `RPL_TIME` = `'391'`
`str` | `RPL_TOPIC` = `'332'`
`str` | `RPL_TRACECLASS` = `'209'`
`str` | `RPL_TRACECONNECTING` = `'201'`
`str` | `RPL_TRACEEND` = `'262'`
`str` | `RPL_TRACEHANDSHAKE` = `'202'`
`str` | `RPL_TRACELINK` = `'200'`
`str` | `RPL_TRACELOG` = `'261'`
`str` | `RPL_TRACENEWTYPE` = `'208'`
`str` | `RPL_TRACEOPERATOR` = `'204'`
`str` | `RPL_TRACERECONNECT` = `'210'`
`str` | `RPL_TRACESERVER` = `'206'`
`str` | `RPL_TRACESERVICE` = `'207'`
`str` | `RPL_TRACEUNKNOWN` = `'203'`
`str` | `RPL_TRACEUSER` = `'205'`
`str` | `RPL_TRYAGAIN` = `'263'`
`str` | `RPL_UMODEIS` = `'221'`
`str` | `RPL_UNAWAY` = `'305'`
`str` | `RPL_UNIQOPIS` = `'325'`
`str` | `RPL_USERHOST` = `'302'`
`str` | `RPL_USERS` = `'393'`
`str` | `RPL_USERSSTART` = `'392'`
`str` | `RPL_VERSION` = `'351'`
`str` | `RPL_WELCOME` = `'001'`
`str` | `RPL_WHOISCHANNELS` = `'319'`
`str` | `RPL_WHOISIDLE` = `'317'`
`str` | `RPL_WHOISOPERATOR` = `'313'`
`str` | `RPL_WHOISSERVER` = `'312'`
`str` | `RPL_WHOISUSER` = `'311'`
`str` | `RPL_WHOREPLY` = `'352'`
`str` | `RPL_WHOWASUSER` = `'314'`
`str` | `RPL_YOUREOPER` = `'381'`
`str` | `RPL_YOURESERVICE` = `'383'`
`str` | `RPL_YOURHOST` = `'002'`
`str` | `SPC` = `' '`
`dict` | `symbolic_to_numeric` = `{'RPL_ENDOFINVITELIST': '347', 'RP...`
`str` | `X_DELIM` = `'\x01'`
`str` | `X_QUOTE` = `'\\'`
`dict` | `xDequoteTable` = `{'a': '\x01', '\\': '\\'}`
`SRE_Pattern` | `xEscape_re` = `\\.`
`dict` | `xQuoteTable` = `{'\x01': '\\a', '\\': '\\\\'}`

Function Details

### ctcpExtract(message) Extract CTCP data from a string. Returns a dictionary with two items: `'extended'`: a list of CTCP (tag, data) tuples `'normal'`: a list of strings which were not inside a CTCP delimeter

### ctcpStringify(messages) Parameters: `messages` (type=a list of extended messages. An extended message is a (tag, data) tuple, where 'data' may be `None`, a string, or a list of strings to be joined with whitespace.) Returns: String

### dccDescribe(data) Given the data chunk from a DCC query, return a descriptive string.

### fileSize(file) I'll try my damndest to determine the size of this file object.

### parsemsg(s) Breaks a message from an IRC server into its prefix, command, and arguments.

### split(str, length=80) I break a message into multiple lines. I prefer to break at whitespace near str[length]. I also break at \n. Returns: list of strings

Variable Details

    ### __version__ Type: `str` Value:
        '1.94'

    ### CHANNEL_PREFIXES Type: `str` Value:
        '&#!+'

    ### CR Type: `str` Value:
        '\r'

    ### ERR_ALREADYREGISTRED Type: `str` Value:
        '462'

    ### ERR_BADCHANMASK Type: `str` Value:
        '476'

    ### ERR_BADCHANNELKEY Type: `str` Value:
        '475'

    ### ERR_BADMASK Type: `str` Value:
        '415'

    ### ERR_BANLISTFULL Type: `str` Value:
        '478'

    ### ERR_BANNEDFROMCHAN Type: `str` Value:
        '474'

    ### ERR_CANNOTSENDTOCHAN Type: `str` Value:
        '404'

    ### ERR_CANTKILLSERVER Type: `str` Value:
        '483'

    ### ERR_CHANNELISFULL Type: `str` Value:
        '471'

    ### ERR_CHANOPRIVSNEEDED Type: `str` Value:
        '482'

    ### ERR_ERRONEUSNICKNAME Type: `str` Value:
        '432'

    ### ERR_FILEERROR Type: `str` Value:
        '424'

    ### ERR_INVITEONLYCHAN Type: `str` Value:
        '473'

    ### ERR_KEYSET Type: `str` Value:
        '467'

    ### ERR_NEEDMOREPARAMS Type: `str` Value:
        '461'

    ### ERR_NICKCOLLISION Type: `str` Value:
        '436'

    ### ERR_NICKNAMEINUSE Type: `str` Value:
        '433'

    ### ERR_NOADMININFO Type: `str` Value:
        '423'

    ### ERR_NOCHANMODES Type: `str` Value:
        '477'

    ### ERR_NOLOGIN Type: `str` Value:
        '444'

    ### ERR_NOMOTD Type: `str` Value:
        '422'

    ### ERR_NONICKNAMEGIVEN Type: `str` Value:
        '431'

    ### ERR_NOOPERHOST Type: `str` Value:
        '491'

    ### ERR_NOORIGIN Type: `str` Value:
        '409'

    ### ERR_NOPERMFORHOST Type: `str` Value:
        '463'

    ### ERR_NOPRIVILEGES Type: `str` Value:
        '481'

    ### ERR_NORECIPIENT Type: `str` Value:
        '411'

    ### ERR_NOSERVICEHOST Type: `str` Value:
        '492'

    ### ERR_NOSUCHCHANNEL Type: `str` Value:
        '403'

    ### ERR_NOSUCHNICK Type: `str` Value:
        '401'

    ### ERR_NOSUCHSERVER Type: `str` Value:
        '402'

    ### ERR_NOSUCHSERVICE Type: `str` Value:
        '408'

    ### ERR_NOTEXTTOSEND Type: `str` Value:
        '412'

    ### ERR_NOTONCHANNEL Type: `str` Value:
        '442'

    ### ERR_NOTOPLEVEL Type: `str` Value:
        '413'

    ### ERR_NOTREGISTERED Type: `str` Value:
        '451'

    ### ERR_PASSWDMISMATCH Type: `str` Value:
        '464'

    ### ERR_RESTRICTED Type: `str` Value:
        '484'

    ### ERR_SUMMONDISABLED Type: `str` Value:
        '445'

    ### ERR_TOOMANYCHANNELS Type: `str` Value:
        '405'

    ### ERR_TOOMANYTARGETS Type: `str` Value:
        '407'

    ### ERR_UMODEUNKNOWNFLAG Type: `str` Value:
        '501'

    ### ERR_UNAVAILRESOURCE Type: `str` Value:
        '437'

    ### ERR_UNIQOPPRIVSNEEDED Type: `str` Value:
        '485'

    ### ERR_UNKNOWNCOMMAND Type: `str` Value:
        '421'

    ### ERR_UNKNOWNMODE Type: `str` Value:
        '472'

    ### ERR_USERNOTINCHANNEL Type: `str` Value:
        '441'

    ### ERR_USERONCHANNEL Type: `str` Value:
        '443'

    ### ERR_USERSDISABLED Type: `str` Value:
        '446'

    ### ERR_USERSDONTMATCH Type: `str` Value:
        '502'

    ### ERR_WASNOSUCHNICK Type: `str` Value:
        '406'

    ### ERR_WILDTOPLEVEL Type: `str` Value:
        '414'

    ### ERR_YOUREBANNEDCREEP Type: `str` Value:
        '465'

    ### ERR_YOUWILLBEBANNED Type: `str` Value:
        '466'

    ### LF Type: `str` Value:
        '''
        '''

    ### M_QUOTE Type: `str` Value:
        '\x10'

    ### mDequoteTable Type: `dict` Value:
        {'0': '\x00', 'r': '\r', '\x10': '\x10', 'n': '\n'}

    ### mEscape_re Type: `SRE_Pattern` Value:
        .

    ### mQuoteTable Type: `dict` Value:
        {'\x00': '\x100', '\n': '\x10n', '\r': '\x10r', '\x10': '\x10\x10'}

    ### NL Type: `str` Value:
        '''
        '''

    ### NUL Type: `str` Value:
        '\x00'

    ### numeric_to_symbolic Type: `dict` Value:
        {'005': 'RPL_BOUNCE',
         '210': 'RPL_TRACERECONNECT',
         '211': 'RPL_STATSLINKINFO',
         '212': 'RPL_STATSCOMMANDS',
         '219': 'RPL_ENDOFSTATS',
         '242': 'RPL_STATSUPTIME',
         '331': 'RPL_NOTOPIC',
         '347': 'RPL_ENDOFINVITELIST',
        ...

    ### RPL_ADMINEMAIL Type: `str` Value:
        '259'

    ### RPL_ADMINLOC Type: `str` Value:
        '258'

    ### RPL_ADMINME Type: `str` Value:
        '256'

    ### RPL_AWAY Type: `str` Value:
        '301'

    ### RPL_BANLIST Type: `str` Value:
        '367'

    ### RPL_BOUNCE Type: `str` Value:
        '005'

    ### RPL_CHANNELMODEIS Type: `str` Value:
        '324'

    ### RPL_CREATED Type: `str` Value:
        '003'

    ### RPL_ENDOFBANLIST Type: `str` Value:
        '368'

    ### RPL_ENDOFEXCEPTLIST Type: `str` Value:
        '349'

    ### RPL_ENDOFINFO Type: `str` Value:
        '374'

    ### RPL_ENDOFINVITELIST Type: `str` Value:
        '347'

    ### RPL_ENDOFLINKS Type: `str` Value:
        '365'

    ### RPL_ENDOFMOTD Type: `str` Value:
        '376'

    ### RPL_ENDOFNAMES Type: `str` Value:
        '366'

    ### RPL_ENDOFSTATS Type: `str` Value:
        '219'

    ### RPL_ENDOFUSERS Type: `str` Value:
        '394'

    ### RPL_ENDOFWHO Type: `str` Value:
        '315'

    ### RPL_ENDOFWHOIS Type: `str` Value:
        '318'

    ### RPL_ENDOFWHOWAS Type: `str` Value:
        '369'

    ### RPL_EXCEPTLIST Type: `str` Value:
        '348'

    ### RPL_INFO Type: `str` Value:
        '371'

    ### RPL_INVITELIST Type: `str` Value:
        '346'

    ### RPL_INVITING Type: `str` Value:
        '341'

    ### RPL_ISON Type: `str` Value:
        '303'

    ### RPL_LINKS Type: `str` Value:
        '364'

    ### RPL_LIST Type: `str` Value:
        '322'

    ### RPL_LISTEND Type: `str` Value:
        '323'

    ### RPL_LISTSTART Type: `str` Value:
        '321'

    ### RPL_LUSERCHANNELS Type: `str` Value:
        '254'

    ### RPL_LUSERCLIENT Type: `str` Value:
        '251'

    ### RPL_LUSERME Type: `str` Value:
        '255'

    ### RPL_LUSEROP Type: `str` Value:
        '252'

    ### RPL_LUSERUNKNOWN Type: `str` Value:
        '253'

    ### RPL_MOTD Type: `str` Value:
        '372'

    ### RPL_MOTDSTART Type: `str` Value:
        '375'

    ### RPL_MYINFO Type: `str` Value:
        '004'

    ### RPL_NAMREPLY Type: `str` Value:
        '353'

    ### RPL_NOTOPIC Type: `str` Value:
        '331'

    ### RPL_NOUSERS Type: `str` Value:
        '395'

    ### RPL_NOWAWAY Type: `str` Value:
        '306'

    ### RPL_REHASHING Type: `str` Value:
        '382'

    ### RPL_SERVLIST Type: `str` Value:
        '234'

    ### RPL_SERVLISTEND Type: `str` Value:
        '235'

    ### RPL_STATSCOMMANDS Type: `str` Value:
        '212'

    ### RPL_STATSLINKINFO Type: `str` Value:
        '211'

    ### RPL_STATSOLINE Type: `str` Value:
        '243'

    ### RPL_STATSUPTIME Type: `str` Value:
        '242'

    ### RPL_SUMMONING Type: `str` Value:
        '342'

    ### RPL_TIME Type: `str` Value:
        '391'

    ### RPL_TOPIC Type: `str` Value:
        '332'

    ### RPL_TRACECLASS Type: `str` Value:
        '209'

    ### RPL_TRACECONNECTING Type: `str` Value:
        '201'

    ### RPL_TRACEEND Type: `str` Value:
        '262'

    ### RPL_TRACEHANDSHAKE Type: `str` Value:
        '202'

    ### RPL_TRACELINK Type: `str` Value:
        '200'

    ### RPL_TRACELOG Type: `str` Value:
        '261'

    ### RPL_TRACENEWTYPE Type: `str` Value:
        '208'

    ### RPL_TRACEOPERATOR Type: `str` Value:
        '204'

    ### RPL_TRACERECONNECT Type: `str` Value:
        '210'

    ### RPL_TRACESERVER Type: `str` Value:
        '206'

    ### RPL_TRACESERVICE Type: `str` Value:
        '207'

    ### RPL_TRACEUNKNOWN Type: `str` Value:
        '203'

    ### RPL_TRACEUSER Type: `str` Value:
        '205'

    ### RPL_TRYAGAIN Type: `str` Value:
        '263'

    ### RPL_UMODEIS Type: `str` Value:
        '221'

    ### RPL_UNAWAY Type: `str` Value:
        '305'

    ### RPL_UNIQOPIS Type: `str` Value:
        '325'

    ### RPL_USERHOST Type: `str` Value:
        '302'

    ### RPL_USERS Type: `str` Value:
        '393'

    ### RPL_USERSSTART Type: `str` Value:
        '392'

    ### RPL_VERSION Type: `str` Value:
        '351'

    ### RPL_WELCOME Type: `str` Value:
        '001'

    ### RPL_WHOISCHANNELS Type: `str` Value:
        '319'

    ### RPL_WHOISIDLE Type: `str` Value:
        '317'

    ### RPL_WHOISOPERATOR Type: `str` Value:
        '313'

    ### RPL_WHOISSERVER Type: `str` Value:
        '312'

    ### RPL_WHOISUSER Type: `str` Value:
        '311'

    ### RPL_WHOREPLY Type: `str` Value:
        '352'

    ### RPL_WHOWASUSER Type: `str` Value:
        '314'

    ### RPL_YOUREOPER Type: `str` Value:
        '381'

    ### RPL_YOURESERVICE Type: `str` Value:
        '383'

    ### RPL_YOURHOST Type: `str` Value:
        '002'

    ### SPC Type: `str` Value:
        ' '

    ### symbolic_to_numeric Type: `dict` Value:
        {'ERR_INVITEONLYCHAN': '473',
         'ERR_KEYSET': '467',
         'ERR_NICKNAMEINUSE': '433',
         'ERR_TOOMANYTARGETS': '407',
         'RPL_CHANNELMODEIS': '324',
         'RPL_ENDOFINVITELIST': '347',
         'RPL_INFO': '371',
         'RPL_UNAWAY': '305',
        ...

    ### X_DELIM Type: `str` Value:
        '\x01'

    ### X_QUOTE Type: `str` Value:
        '\\'

    ### xDequoteTable Type: `dict` Value:
        {'a': '\x01', '\\': '\\'}

    ### xEscape_re Type: `SRE_Pattern` Value:
        \\.

    ### xQuoteTable Type: `dict` Value:
        {'\x01': '\\a', '\\': '\\\\'}

Generated by Epydoc 2.0 on Sat May 15 20:08:10 2004 | http://epydoc.sf.net