Limnoria/tools/generate-plugin-documentation.py

428 lines
15 KiB
Python
Executable File

#!/usr/bin/env python
###
# Copyright (c) 2002, Jeremiah Fincher
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions, and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions, and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of the author of this software nor the name of
# contributors to this software may be used to endorse or promote products
# derived from this software without specific prior written consent.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
###
import os
import cgi
import imp
import sys
import os.path
import textwrap
import supybot
import supybot.world as world
world.documenting = True
if not os.path.exists('test-conf'):
os.mkdir('test-conf')
registryFilename = os.path.join('test-conf', 'test.conf')
fd = file(registryFilename, 'w')
fd.write("""
supybot.directories.data: test-data
supybot.directories.conf: test-conf
supybot.directories.log: test-logs
""")
fd.close()
import supybot.registry as registry
registry.open(registryFilename)
import supybot.log as log
import supybot.conf as conf
import supybot.utils as utils
import supybot.irclib as irclib
import supybot.callbacks as callbacks
commandDict = {}
firstChars = {}
def genHeader(title, meta=''):
return '''
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html lang="en-us">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>%s</title>
<link rel="stylesheet" type="text/css"
href="http://supybot.sourceforge.net/css/supybot.css">
%s
</head>
<body><div>
''' % (title, meta)
def genNavbar(path, cActive=True):
download = 'http://sourceforge.net/project/showfiles.php?group_id=58965'
bug = 'http://sourceforge.net/tracker/?func=add&amp;group_id=58965&amp;'\
'atid=489447'
if cActive:
command = '<li>\n<a href="%scommands.html">Commands Index</a>\n</li>'\
% path[3:]
else:
command = '<li class="plain">\nCommands Index\n</li>'
return '''
<div id="nav">
<ul>
<li>
<a href="%s">Home</a>
</li>
<li>
<a href="http://sourceforge.net/projects/supybot/">Sourceforge Project</a>
</li>
<li>
<a href="%s">
Download
</a>
</li>
<li>
<a
href="%s">
Submit a Bug
</a>
</li>
%s
<li>
<a href="http://forums.supybot.org"/>Forums</a>
</li>
</ul>
<p style="clear:both; margin:0; padding:0;"></p>
</div>
''' % (path, download, bug, command)
def genFooter():
return '''
</div>
<div style="text-align: center"><br /><!-- Buttons -->
<a href="http://validator.w3.org/check/referer"><img
src="http://www.w3.org/Icons/valid-html401"
alt="Valid HTML 4.01!" height="31" width="88" /></a>
<a href="http://jigsaw.w3.org/css-validator/check/referer"><img
src="http://jigsaw.w3.org/css-validator/images/vcss"
alt="Valid CSS!" /></a>
<a href="http://sourceforge.net"><img
src="http://sourceforge.net/sflogo.php?group_id=58965&amp;type=1"
width="88" height="31" alt="SourceForge.net Logo" /></a>
</div>
</body>
</html>
'''
def prepIndex():
directory = os.path.join('docs', 'plugins')
if not os.path.exists(directory):
os.mkdir(directory)
fd = file(os.path.join('docs', 'plugins.html'), 'w')
fd.write(textwrap.dedent('''
%s
<div class="maintitle">Supybot Plugin Documentation Index</div>
%s
<div class="mainbody">
''' % (genHeader('Supybot Plugin Documentation'), genNavbar('../'))))
fd.close()
def genConfigSection(fd, item, toplevel=False):
confVars = item.getValues(getChildren=False, fullNames=False)
if not confVars:
return
fd.write('''<ul>''')
for (c, v) in confVars:
if not toplevel:
name = '.%s' % c
else:
name = v._name
fd.write(textwrap.dedent('''
<li id="%s"><strong>%s</strong>
''' % (v._name, name)))
try:
default = str(v)
if isinstance(v._default, basestring):
default = utils.dqrepr(default)
help = v.help()
channelValue = v.channelValue
except registry.NonExistentRegistryEntry:
pass
else:
help = cgi.escape(help)
default = cgi.escape(default)
fd.write(textwrap.dedent('''
<ul>
<li class="nonPlugin">Default: %s</li>
<li class="nonPlugin">Channel Specific: %s</li>
<li class="nonPlugin">Help: %s</li></ul>
''' % (default, channelValue, help)))
genConfigSection(fd, v)
fd.write(textwrap.dedent('''</li>'''))
fd.write('''</ul>''')
def makeNonPluginDocumentation():
fd = file(os.path.join('docs', 'config.html'), 'w')
title = 'Non-plugin configuration variables for Supybot'
meta = '''
<link rel="home" title="Supybot Homepage" href="../index.html">
'''
fd.write(textwrap.dedent('''
%s
<div class="mainbody" style="padding: 0;">
%s
<div style="margin: 1em;">
<h4 style="text-align: center;">Non-plugin configuration variables for
Supybot</h4>
''' % (genHeader(title, meta), genNavbar('../'))))
genConfigSection(fd, conf.supybot, toplevel=True)
fd.write(textwrap.dedent('''
</div>
</div>
</div>
<div style="text-align: center;">
<br />
%s
''' % (genFooter(),)))
fd.close()
def makePluginDocumentation(pluginWindow):
global commandDict
global firstChars
trClasses = { 'even':'odd', 'odd':'even' }
trClass = 'even'
(pluginName, module, plugin) = pluginWindow[1]
if getattr(module, "deprecated", False):
deprecated = textwrap.dedent("""
<div class="deprecated">This plugin is deprecated. That means that it
is probably broken and no one cares about it enought to fix it or the
3rd party module it may be using. If you want this plugin to work,
adopt it or provide a patch in our patch tracker on Sourceforge.</div>
""")
else:
deprecated = ''
print 'Generating documentation for %s.py' % pluginName
prev = pluginWindow[0][0] or '../plugins'
next = pluginWindow[2][0] or '../plugins'
# can't use string.capitalize() because it lowercases every character
# except the first. must create our own capitalized names
cpluginName = '%s%s' % (pluginName[0].upper(), pluginName[1:])
temp = prev.strip('./')
cprev = '%s%s' % (temp[0].upper(), temp[1:])
temp = next.strip('./')
cnext = '%s%s' % (temp[0].upper(), temp[1:])
directory = os.path.join('docs', 'plugins')
if not os.path.exists(directory):
os.mkdir(directory)
id = file(os.path.join('docs', 'plugins.html'), 'a')
id.write('<strong><a href="plugins/%s.html">%s</a></strong>\n' %
(pluginName, cpluginName))
fd = file(os.path.join(directory,'%s.html' % pluginName), 'w')
title = 'Documentation for the %s plugin for Supybot' % pluginName
meta = '''
<link rel="home" title="Plugin Documentation Index" href="../plugins.html">
<link rel="next" href="%s.html">
<link rel="previous" href="%s.html">
''' % (next, prev)
pluginhelp = getattr(module.Class, '__doc__', False)
if pluginhelp:
pluginhelp = '<div class="pluginhelp">%s</div>' %\
cgi.escape(pluginhelp)
else:
pluginhelp = ''
fd.write(textwrap.dedent('''
%s
<div class="plugintitle">%s</div>
%s
%s
<div class="mainbody" style="padding: 0;">
%s
<table>
<tr><th colspan="3">Commands for %s</th></tr>
<tr class="trheader"><td>Command</td><td>Args</td><td>
Detailed Help</td></tr>
''' % (genHeader(title, meta),
cgi.escape(module.__doc__ or ""),
pluginhelp,
deprecated,
genNavbar('../../'),
pluginName)))
attrs = [x for x in dir(plugin) if plugin.isCommand(x) and
callbacks.canonicalName(x) == x]
id.write('(%s)<br />\n' % ', '.join(attrs))
for attr in attrs:
if attr in commandDict:
commandDict[attr].append(pluginName)
else:
commandDict[attr] = [pluginName]
if attr[0] not in firstChars:
firstChars[attr[0]] = ''
method = getattr(plugin, attr)
if hasattr(method, '__doc__'):
doclines = method.__doc__.splitlines()
help = doclines.pop(0)
morehelp = 'This command has no detailed help.'
if doclines:
doclines = filter(None, doclines)
doclines = map(str.strip, doclines)
morehelp = ' '.join(doclines)
help = cgi.escape(help)
morehelp = cgi.escape(morehelp)
trClass = trClasses[trClass]
fd.write(textwrap.dedent('''
<tr class="%s" id="%s"><td>%s</td><td>%s</td>
<td class="detail">%s</td></tr>
''' % (trClass, attr, attr, help, morehelp)))
try:
pluginconf = conf.supybot.plugins.get(pluginName)
except registry.NonExistentRegistryEntry:
fd.write('</table>\n')
pass
else:
fd.write(textwrap.dedent(
'''</table><br />
<div>
<h4 style="text-align: center">Configuration Variables for
the %s plugin</h4>
''' % pluginName))
genConfigSection(fd, pluginconf, toplevel=True)
fd.write(textwrap.dedent('''</div>'''))
fd.write(textwrap.dedent('''
</div>
</div>
<div style="text-align: center;">
<br />
<a href="%s.html">&lt;- %s</a> |
<a href="../plugins.html">Plugins Index</a> |
<a href="../commands.html">Commands Index</a> |
<a href="%s.html">%s -&gt;</a>
%s
''' % (prev, cprev, next, cnext, genFooter())))
fd.close()
id.close()
def finishIndex():
directory = 'docs'
if not os.path.exists(directory):
os.mkdir(directory)
fd = file(os.path.join(directory, 'plugins.html'), 'a')
fd.write(textwrap.dedent('</div>\n%s' % genFooter()))
fd.close()
def makeCommandsIndex():
from string import ascii_lowercase
global commandDict
global firstChars
directory = 'docs'
if not os.path.exists(directory):
os.mkdir(directory)
fd = file(os.path.join(directory, 'commands.html'), 'w')
title = 'Supybot Commands Index'
fd.write(textwrap.dedent('''
%s
<div class="maintitle">%s</div>
%s
<div class="mainbody" style="text-align: center;">
''' % (genHeader(title),
title,
genNavbar('../', cActive=False))))
commands = [c for c in commandDict.iterkeys()]
commands.sort()
for i in ascii_lowercase:
if i in firstChars:
fd.write('<a href="#%s">%s</a> \n' % (i, i.capitalize()))
else:
fd.write('%s ' % i.capitalize())
firstChars.clear()
fd.write('</div>\n<br />')
pluginLink = '<a href="plugins/%s.html#%s">%s</a>'
for command in commands:
c = command[0]
if c not in firstChars:
if firstChars:
fd.write('</div><br />\n')
fd.write('\n<div class="whitebox">')
firstChars[c] = ''
fd.write('<div id="%s" class="letter">%s</div>\n' %
(c, c.capitalize()))
plugins = commandDict[command]
plugins.sort()
fd.write('<strong>%s</strong> (%s)<br />\n' %
(command,
', \n\t'.join([pluginLink % (p,command,p) for p in plugins])))
fd.write('\n</div>')
fd.write(textwrap.dedent(genFooter()))
fd.close()
def genPlugins():
for directory in conf.supybot.directories.plugins():
for filename in os.listdir(directory):
if filename.endswith('.py') and filename[0].isupper():
pluginName = filename.split('.')[0]
moduleInfo = imp.find_module(pluginName,
conf.supybot.directories.plugins()
)
try:
module = imp.load_module(pluginName, *moduleInfo)
except Exception, e:
print 'Couldn\'t load %s: %s' % \
(pluginName, utils.exnToString(e))
continue
if not hasattr(module, 'Class'):
print '%s is not a plugin.' % filename
continue
try:
plugin = module.Class()
except Exception, e:
print '%s could not be loaded: %s' % (filename,
utils.exnToString(e))
continue
if isinstance(plugin, callbacks.Privmsg) and not \
isinstance(plugin, callbacks.PrivmsgRegexp):
yield (pluginName, module, plugin)
if __name__ == '__main__':
if not os.path.exists(conf.supybot.directories.data()):
os.mkdir(conf.supybot.directories.data())
if not os.path.exists(conf.supybot.directories.conf()):
os.mkdir(conf.supybot.directories.conf())
if not os.path.exists(conf.supybot.directories.log()):
os.mkdir(conf.supybot.directories.log())
makeNonPluginDocumentation()
prepIndex()
plugins = [p for p in genPlugins()]
plugins.sort()
plugins.insert(0, [None])
plugins.append([None])
for pluginWindow in window(plugins, 3):
makePluginDocumentation(pluginWindow)
finishIndex()
makeCommandsIndex()
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: