<filename>Sketches/Old/KamaeliaDoc.py
#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright 2010 British Broadcasting Corporation and Kamaelia Contributors(1)
#
# (1) Kamaelia Contributors are listed in the AUTHORS file and at
#     http://www.kamaelia.org/AUTHORS - please extend this file,
#     not this notice.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import textwrap
import inspect
from docutils import core

# These don't follow the mould for various reasons.
#    Kamaelia.Chassis.Prefab.JoinChooserToCarousel
#    Kamaelia.File.Reading.RateControlledFileReader
#    Kamaelia.File.Reading.ReusableFileReader
#    Kamaelia.File.Reading.RateControlledReusableFileReader
#    Kamaelia.File.Reading.FixedRateControlledReusableFileReader
#    "Kamaelia.Util.TestResultComponent" : ["TestResultComponent"],
#    "Kamaelia.Util.MarshallComponent" : ["BasicMarshallComponent"],
COMPONENTS = {
    "Kamaelia.Chassis.Carousel" : ["Carousel"],
    "Kamaelia.Chassis.ConnectedServer" : ["SimpleServer"],
    "Kamaelia.Codec.Dirac" : ["DiracDecoder", "DiracEncoder"],
    "Kamaelia.Codec.RawYUVFramer" : ["RawYUVFramer"],
    "Kamaelia.File.Reading" : ["PromptedFileReader"],
    "Kamaelia.File.Writing" : ["SimpleFileWriter"],
    "Kamaelia.Internet.ConnectedSocketAdapter" : ["ConnectedSocketAdapter"],
    "Kamaelia.Internet.Multicast_receiver" : ["Multicast_receiver"],
    "Kamaelia.Internet.Multicast_sender" : ["Multicast_sender"],
    "Kamaelia.Internet.Multicast_transceiver" : ["Multicast_transceiver"],
    "Kamaelia.Internet.Selector" : ["selectorComponent"],
    "Kamaelia.Internet.Simulate.BrokenNetwork" : ["Duplicate","Throwaway","Reorder"],
    "Kamaelia.Internet.TCPClient"   : ["TCPClient"],
    "Kamaelia.Internet.TCPServer"   : ["TCPServer"],
    "Kamaelia.Internet.ThreadedTCPClient" : ["ThreadedTCPClient"],
    "Kamaelia.MimeRequestComponent"    : ["MimeRequestComponent"],

    "Kamaelia.Util.Chargen" : ["Chargen"],
    "Kamaelia.Util.Chooser" : ["Chooser","ForwardIteratingChooser"],
    "Kamaelia.Util.Comparator" : ["comparator"],
    "Kamaelia.Util.ConsoleEcho"     : ["consoleEchoer"],
    "Kamaelia.Util.Fanout" : ["fanout"],
    "Kamaelia.Util.FilterComponent" : ["FilterComponent"],
    "Kamaelia.Util.Graphline" : ["Graphline"],
    "Kamaelia.Util.Introspector"    : ["Introspector"],
    "Kamaelia.Util.LossyConnector" : ["lossyConnector"],
    "Kamaelia.Util.Marshalling" : ["Marshaller","DeMarshaller"],
    "Kamaelia.Util.NullSinkComponent" : ["nullSinkComponent"],
    "Kamaelia.Util.PipelineComponent" : ["pipeline"],
    "Kamaelia.Util.RateFilter" : ["MessageRateLimit", "ByteRate_RequestControl", "VariableByteRate_RequestControl"],
    "Kamaelia.Util.Splitter" : ["Splitter", "PlugSplitter", "Plug"],
    "Kamaelia.Util.ToStringComponent" : ["ToStringComponent"],
    "Kamaelia.Util.passThrough" : ["passThrough"],

    "Kamaelia.SingleServer"         : ["SingleServer"],
    "Kamaelia.ReadFileAdaptor"      : ["ReadFileAdaptor"],
    "Kamaelia.vorbisDecodeComponent" : [ "VorbisDecode", "AOAudioPlaybackAdaptor" ],
}

class plainRender(object):
    def itemRateControlledReusableFileReaderList(self, items):
        result = []
        for item in items:
            result.append("   "+ str(item[0])+ " : "+ str(item[1]))
        return "\n".join(result)+"\n"
    def heading(self, label, level=4):
        if level == 2: 
            u = "".join(["*" for x in label])
            return "\n"+label + "\n"+ u + "\n"
        if level == 3: return label + "\n"
        if level == 4: return label + ":" + "\n"
        if level == 5: return label + ":"
    def preformat(self, somestring):
        lines = somestring.split("\n")
        L = []
        for l in lines:
            L.append("    "+l+"\n")
        return "".join(L)
    def divider(self):
        return "\n"
    def start(self): return ""
    def stop(self): return ""

class htmlRender(object):
    def itemList(self, items):
        result = []
        for item in items:
            result.append("   "+ str(item[0])+ " : "+ str(item[1]))
        return "<ul><li>"+ "\n<li>".join(result)+"\n</ul>"

    def heading(self, label, level=4):
        if level == 2: return "<h2>" + label + "</h2>\n"
        if level == 3: return "<h3>" + label + "</h3>\n"
        if level == 4: return "<h4>" + label + "</h4>\n"
        if level == 5: return "<h5>" + label + "</h5>\n"

    def preformat(self, somestring):
        lines = somestring.split("\n")
        L = []
        for l in lines:
            L.append("    "+l+"\n")
        return "<pre>\n"+"".join(L)+"\n</pre>\n"

    def preformat(self, somestring):
        doc = core.publish_parts(somestring,writer_name="html")["whole"]
        doclines=doc.split("\n")
        while """<div class="document">""" not in doclines[0]:
            doclines = doclines[1:]
        doclines = doclines[1:]
        while """</div>""" not in doclines[-1]:
            doclines = doclines[:-1]
        try:
            while """</div>""" in doclines[-1]:
                doclines = doclines[:-1]
        except IndexError:
            pass
        doc = "\n".join(doclines)
        return doc

    def divider(self):
        return "\n"
    def start(self): return "<html><body>\n"
    def stop(self): 
        return """\
<HR>
<h2> Feedback </h2>
<P>Got a problem with the documentation? Something unclear, could
be clearer? Want to help with improving? Constructive criticism,
preferably in the form of suggested rewording is very welcome.

<P>Please leave the feedback 
<a href="http://kamaelia.sourceforge.net/cgi-bin/blog/blog.cgi?rm=addpostcomment&postid=1131454685"> 
here, in reply to the documentation thread in the Kamaelia blog</a>. 
</body></html>
"""

class docFormatter(object):
    def __init__(self, renderer=plainRender):
        self.renderer = renderer()

    def boxes(self,label, boxes):
        items = []
        for box in boxes:
            try:
                description = boxes[box]
            except KeyError:
                description = ""
            except TypeError:
                description = "Code uses old style inbox/outbox description - no metadata available"
            items.append((box, description))

        return self.renderer.heading(label) + self.renderer.itemList(items) + self.renderer.divider()

    def name(self,name):
        return self.renderer.heading(name)

    def methodName(self,name):
        return self.renderer.heading(name,3)

    def docString(self,docstring, main=False):
        if docstring is None:
            docstring = " "
        lines = "\n".split(docstring)
        if len(lines)>1:
            line1 = textwrap.dedent(lines[0])
            rest = textwrap.dedent("\n".join(lines[1:]))
            docstring = line1+"\n"+rest
        else:
            docstring=textwrap.dedent(docstring)

        while docstring[0] == "\n":
            docstring = docstring[1:]
        while docstring[-1] == "\n":
            docstring = docstring[:-1]
        pre = ""
        if main:
            pre = self.renderer.divider()

        return pre + self.renderer.preformat(docstring)+ self.renderer.divider()

    def SectionHeader(self, header):
        return self.renderer.heading(header, 2)

    def paragraph(self, para):
        return self.renderer.divider()+ textwrap.fill(para)+ self.renderer.divider()

    def formatArgSpec(self, argspec):
        return "(" + ", ".join(argspec[0]) + ")"

    def formatMethodDocStrings(self,X):
        r = ""
        for method in sorted([x for x in inspect.classify_class_attrs(X) if x[2] == X and x[1] == "method"]):
            methodHead = method[0]+self.formatArgSpec(inspect.getargspec(method[3]))
            r += self.methodName(methodHead)+ self.docString(method[3].__doc__)

        return r

    def formatClassStatement(self, name, bases):
        return "class "+ name+"("+",".join([str(base)[8:-2] for base in bases])+")"

    def formatComponent(self, X):
        return self.SectionHeader(self.formatClassStatement(X.__name__, X.__bases__)) + \
               self.docString(X.__doc__, main=True) + \
               self.boxes("Inboxes", X.Inboxes) + \
               self.boxes("Outboxes", X.Outboxes) + \
               self.SectionHeader("Methods defined here")+ \
               self.paragraph("[[boxright][[include][file=Components/MethodNote.html][croptop=1][cropbottom=1] ] ]") +\
               self.formatMethodDocStrings(X)

    def preamble(self): return self.renderer.start()
    def postamble(self): return self.renderer.stop()

formatter = docFormatter(htmlRender)
#formatter = docFormatter(plainRender)


for MODULE in COMPONENTS:
    module = __import__(MODULE, [], [], COMPONENTS[MODULE])
    for COMPONENT in COMPONENTS[MODULE]:
        print "Processing: "+MODULE+"."+COMPONENT+" ..."
        F = open(MODULE+"."+COMPONENT+".html","w")
        X = getattr(module, COMPONENT)
        F.write(formatter.preamble())
        F.write("<h1>"+ MODULE+"."+COMPONENT+"</h1>\n")
        F.write(formatter.formatComponent(X))
        F.write(formatter.postamble())
        F.close()

