Modules | Files | Inheritance Tree | Inheritance Graph | Name Index | Config
File: Synopsis/Formatter/DocBook.py
    1| #  $Id: DocBook.py,v 1.10 2003/02/02 00:22:44 chalky Exp $
    2| #
    3| #  This file is a part of Synopsis.
    4| #  Copyright (C) 2000, 2001 Stefan Seefeld
    5| #
    6| #  Synopsis is free software; you can redistribute it and/or modify it
    7| #  under the terms of the GNU General Public License as published by
    8| #  the Free Software Foundation; either version 2 of the License, or
    9| #  (at your option) any later version.
   10| #
   11| #  This program is distributed in the hope that it will be useful,
   12| #  but WITHOUT ANY WARRANTY; without even the implied warranty of
   13| #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   14| #  General Public License for more details.
   15| #
   16| #  You should have received a copy of the GNU General Public License
   17| #  along with this program; if not, write to the Free Software
   18| #  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
   19| #  02111-1307, USA.
   20| #
   21| # $Log: DocBook.py,v $
   22| # Revision 1.10  2003/02/02 00:22:44  chalky
   23| # Remove obsolete parseTags method
   24| #
   25| # Revision 1.9  2003/02/01 23:57:37  chalky
   26| # Use new tags
   27| #
   28| # Revision 1.8  2002/11/18 11:49:29  chalky
   29| # Tried to fix it up, but the *synopsis docbook entities aren't what we need.
   30| # Shortcut to what I needed and just wrote a DocFormatter for the Synopsis
   31| # DocBook manual's Config section.
   32| #
   33| # Revision 1.7  2001/07/19 04:03:05  chalky
   34| # New .syn file format.
   35| #
   36| # Revision 1.6  2001/05/25 13:45:49  stefan
   37| # fix problem with getopt error reporting
   38| #
   39| # Revision 1.5  2001/02/13 06:55:23  chalky
   40| # Made synopsis -l work again
   41| #
   42| # Revision 1.4  2001/02/11 19:33:32  stefan
   43| # made the C++ parser formally accept the config object; synopsis now correctly formats if a formatter is present, and only dumps a syn file otherwise; some minor fixes to DocBook
   44| #
   45| # Revision 1.3  2001/02/11 05:39:33  stefan
   46| # first try at a more powerful config framework for synopsis
   47| #
   48| # Revision 1.2  2001/01/31 06:51:24  stefan
   49| # add support for '-v' to all modules; modified toc lookup to use additional url as prefix
   50| #
   51| # Revision 1.1  2001/01/27 06:01:21  stefan
   52| # a first take at a docbook formatter
   53| #
   54| #
   55| """a DocBook formatter (producing Docbook 4.2 XML output"""
   56| # THIS-IS-A-FORMATTER
   57| 
   58| import sys, getopt, os, os.path, string, re
   59| from Synopsis.Core import AST, Type, Util
   60| 
   61| verbose = 0
   62| 
   63| languages = {
   64|     'IDL': 'idl',
   65|     'C++': 'cxx',
   66|     'Python': 'python'
   67|     }
   68| 
   69| class Formatter (Type.Visitor, AST.Visitor):
   70|     """
   71|     The type visitors should generate names relative to the current scope.
   72|     The generated references however are fully scoped names
   73|     """
   74|     def __init__(self, os):
   75|         self.__os = os
   76|         self.__scope = ()
   77|         self.__scopestack = []
   78|         self.__indent = 0
   79| 
   80|     def scope(self): return self.__scope
   81|     def push_scope(self, newscope):
   82|         self.__scopestack.append(self.__scope)
   83|         self.__scope = newscope
   84|     def pop_scope(self):
   85|         self.__scope = self.__scopestack[-1]
   86|         del self.__scopestack[-1]
   87|     def write(self, text):
   88|         """Write some text to the output stream, replacing \n's with \n's and
   89|         indents."""
   90|         indent = ' ' * self.__indent
   91|         self.__os.write(text.replace('\n', '\n'+indent))
   92|     def start_entity(self, __type, **__params):
   93|         """Write the start of an entity, ending with a newline"""
   94|         param_text = ""
   95|         if __params: param_text = " " + string.join(map(lambda p:'%s="%s"'%(p[0].lower(), p[1]), __params.items()))
   96|         self.write("<" + __type + param_text + ">")
   97|         self.__indent = self.__indent + 2
   98|         self.write("\n")
   99|     def end_entity(self, type):
  100|         """Write the end of an entity, starting with a newline"""
  101|         self.__indent = self.__indent - 2
  102|         self.write("\n</" + type + ">")
  103|     def write_entity(self, __type, __body, **__params):
  104|         """Write a single entity on one line (though body may contain
  105|         newlines)"""
  106|         param_text = ""
  107|         if __params: param_text = " " + string.join(map(lambda p:'%s="%s"'%(p[0].lower(), p[1]), __params.items()))
  108|         self.write("<" + __type + param_text + ">")
  109|         self.__indent = self.__indent + 2
  110|         self.write(__body)
  111|         self.__indent = self.__indent - 2
  112|         self.write("</" + __type + ">")
  113|     def entity(self, __type, __body, **__params):
  114|         """Return but do not write the text for an entity on one line"""
  115|         param_text = ""
  116|         if __params: param_text = " " + string.join(map(lambda p:'%s="%s"'%(p[0].lower(), p[1]), __params.items()))
  117|         return "<%s%s>%s</%s>"%(__type, param_text, __body, __type)
  118| 
  119|     def reference(self, ref, label):
  120|         """reference takes two strings, a reference (used to look up the symbol and generated the reference),
  121|         and the label (used to actually write it)"""
  122|         location = self.__toc.lookup(ref)
  123|         if location != "": return href("#" + location, label)
  124|         else: return span("type", str(label))
  125|         
  126|     def label(self, ref):
  127|         location = self.__toc.lookup(Util.ccolonName(ref))
  128|         ref = Util.ccolonName(ref, self.scope())
  129|         if location != "": return name("\"" + location + "\"", ref)
  130|         else: return ref
  131| 
  132|     def type_label(self): return self.__type_label
  133|     #################### Type Visitor ###########################################
  134| 
  135|     def visitBaseType(self, type):
  136|         self.__type_ref = Util.ccolonName(type.name())
  137|         self.__type_label = Util.ccolonName(type.name())
  138|         
  139|     def visitUnknown(self, type):
  140|         self.__type_ref = Util.ccolonName(type.name())
  141|         self.__type_label = Util.ccolonName(type.name(), self.scope())
  142|         
  143|     def visitDeclared(self, type):
  144|         self.__type_label = Util.ccolonName(type.name(), self.scope())
  145|         self.__type_ref = Util.ccolonName(type.name())
  146|         
  147|     def visitModifier(self, type):
  148|         type.alias().accept(self)
  149|         self.__type_ref = string.join(type.premod()) + " " + self.__type_ref + " " + string.join(type.postmod())
  150|         self.__type_label = string.join(type.premod()) + " " + self.__type_label + " " + string.join(type.postmod())
  151|             
  152|     def visitParametrized(self, type):
  153|         type.template().accept(self)
  154|         type_label = self.__type_label + "&lt;"
  155|         parameters_label = []
  156|         for p in type.parameters():
  157|             p.accept(self)
  158|             parameters_label.append(self.__type_label)
  159|         self.__type_label = type_label + string.join(parameters_label, ", ") + "&gt;"
  160| 
  161|     def visitFunctionType(self, type):
  162|         # TODO: this needs to be implemented
  163|         self.__type_ref = 'function_type'
  164|         self.__type_label = 'function_type'
  165| 
  166|     def visitComment(self, comment):
  167|         text = comment.text()
  168|         text = text.replace('\n\n', '</para><para>')
  169|         self.write(self.entity("para", text)+'\n')
  170| 
  171|     #################### AST Visitor ############################################
  172|             
  173|     def visitDeclarator(self, node):
  174|         self.__declarator = node.name()
  175|         for i in node.sizes():
  176|             self.__declarator[-1] = self.__declarator[-1] + "[" + str(i) + "]"
  177| 
  178|     def visitTypedef(self, typedef):
  179|         print "sorry, <typedef> not implemented"
  180| 
  181|     def visitVariable(self, variable):
  182|         self.start_entity("fieldsynopsis")
  183|         variable.vtype().accept(self)
  184|         self.entity("type", self.type_label())
  185|         self.entity("varname", variable.name()[-1])
  186|         self.end_entity("fieldsynopsis")
  187| 
  188|     def visitConst(self, const):
  189|         print "sorry, <const> not implemented"
  190| 
  191|     def visitModule(self, module):
  192|         self.start_entity("section")
  193|         self.write_entity("title", module.type()+" "+Util.ccolonName(module.name()))
  194|         self.write("\n")
  195|         map(self.visitComment, module.comments())
  196|         self.push_scope(module.name())
  197|         for declaration in module.declarations(): declaration.accept(self)
  198|         self.pop_scope()
  199|         self.end_entity("section")
  200| 
  201|     def visitClass(self, clas):
  202|         self.start_entity("classsynopsis", Class=clas.type(), language=languages[clas.language()])
  203|         classname = self.entity("classname", Util.ccolonName(clas.name()))
  204|         self.write_entity("ooclass", classname)
  205|         self.start_entity("classsynopsisinfo")
  206|         if len(clas.parents()):
  207|             for parent in clas.parents(): parent.accept(self)
  208|         self.push_scope(clas.name())
  209|         if clas.comments():
  210|             self.start_entity("textobject")
  211|             map(self.visitComment, clas.comments())
  212|             self.end_entity("textobject")
  213|         self.end_entity("classsynopsisinfo")
  214|         classes = []
  215|         for declaration in clas.declarations():
  216|             # Store classes for later
  217|             if isinstance(declaration, AST.Class):
  218|                 classes.append(declaration)
  219|             else:
  220|                 declaration.accept(self)
  221|         self.pop_scope()
  222|         self.end_entity("classsynopsis")
  223|         # Classes can't be nested (in docbook 4.2), so do them after
  224|         for clas in classes: clas.accept(self)
  225| 
  226|     def visitInheritance(self, inheritance):
  227|         map(lambda a, this=self: this.entity("modifier", a), inheritance.attributes())
  228|         self.entity("classname", Util.ccolonName(inheritance.parent().name(), self.scope()))
  229| 
  230|     def visitParameter(self, parameter):
  231|         self.start_entity("methodparam")
  232|         map(lambda m, this=self: this.write_entity("modifier", m), parameter.premodifier())
  233|         parameter.type().accept(self)
  234|         self.write_entity("type", self.type_label())
  235|         self.write_entity("parameter", parameter.identifier())
  236|         map(lambda m, this=self: this.write_entity("modifier", m), parameter.postmodifier())
  237|         self.end_entity("methodparam")
  238| 
  239|     def visitFunction(self, function):
  240|         print "sorry, <function> not implemented"
  241| 
  242|     def visitOperation(self, operation):
  243|         if operation.language() == "IDL" and operation.type() == "attribute":
  244|             self.start_entity("fieldsynopsis")
  245|             map(lambda m, this=self: this.entity("modifier", m), operation.premodifiers())
  246|             self.write_entity("modifier", "attribute")
  247|             operation.returnType().accept(self)
  248|             self.write_entity("type", self.type_label())
  249|             self.write_entity("varname", operation.realname())
  250|             self.end_entity("fieldsynopsis")
  251|         else:
  252|             self.start_entity("methodsynopsis")
  253|             if operation.language() != "Python":
  254|                ret = operation.returnType()
  255|         if ret:
  256|                    ret.accept(self)
  257|                    self.write_entity("type", self.type_label())
  258|          else:
  259|                self.write_entity("modifier", "def")
  260|             self.write_entity("methodname", Util.ccolonName(operation.realname(), self.scope()))
  261|             for parameter in operation.parameters(): parameter.accept(self)
  262|             map(lambda e, this=self: this.entity("exceptionname", e), operation.exceptions())
  263|             self.end_entity("methodsynopsis")
  264| 
  265|     def visitEnumerator(self, enumerator):
  266|         print "sorry, <enumerator> not implemented"
  267|     def visitEnum(self, enum):
  268|         print "sorry, <enum> not implemented"
  269| 
  270| class DocFormatter (Formatter):
  271|     """A specialized version that just caters for the needs of the DocBook
  272|     manual's Config section. Only modules and classes are printed, and the
  273|     docbook elements classsynopsis etc are not used."""
  274|     def visitComment(self, comment):
  275|         text = comment.text()
  276|         see_tags, attr_tags = [], []
  277|         tags = comment.tags()
  278|         # Parse each of the tags
  279|         for tag in tags:
  280|             name, rest = tag.name(), tag.text()
  281|             if name == '@see':
  282|                see_tags.append(rest)
  283|             elif name == '@attr':
  284|                attr_tags.append(string.split(rest,' ',1))
  285|         # Do the body of the comment
  286|         text = text.replace('\n\n', '</para><para>')
  287|         text = text.replace('<heading>', '<emphasis>')
  288|         text = text.replace('</heading>', '</emphasis>')
  289|         text = text.replace('<example>', '<programlisting>')
  290|         text = text.replace('</example>', '</programlisting>')
  291|         self.write(self.entity("para", text)+'\n')
  292|         # Do the attributes
  293|         if len(attr_tags):
  294|             self.start_entity('variablelist')
  295|             self.write_entity('title', 'Attributes:')
  296|             for attr, desc in attr_tags:
  297|                self.start_entity('varlistentry')
  298|                self.write_entity('term', attr)
  299|                self.write_entity('listitem', self.entity('para', desc))
  300|                self.end_entity('varlistentry')
  301|             self.end_entity('variablelist')
  302|         # Do the see-also
  303|         if len(see_tags):
  304|             self.start_entity('itemizedlist')
  305|             self.write_entity('title', 'See also:')
  306|             for text in see_tags:
  307|                self.write_entity('listitem', self.entity('para', text))
  308|             self.end_entity('itemizedlist')
  309| 
  310|     def visitModule(self, module):
  311|         self.start_entity("section")
  312|         self.write_entity("title", module.type()+" "+Util.ccolonName(module.name()))
  313|         self.write("\n")
  314|         map(self.visitComment, module.comments())
  315|         self.end_entity("section")
  316|         self.push_scope(module.name())
  317|         for declaration in module.declarations():
  318|             if isinstance(declaration, AST.Class):
  319|                 self.visitClass(declaration)
  320|         self.pop_scope()
  321| 
  322|     def visitClass(self, clas):
  323|         self.start_entity("section")
  324|         if len(clas.name()) > 3 and clas.name()[:2] == ("Config.py", "Base"):
  325|             self.write_entity("title", clas.name()[2]+" class "+Util.ccolonName(clas.name()[3:], self.scope()))
  326|         else:
  327|             self.write_entity("title", "Class "+Util.ccolonName(clas.name(), self.scope()))
  328|         if len(clas.parents()):
  329|             for parent in clas.parents():
  330|                self.visitInheritance(parent)
  331|         map(self.visitComment, clas.comments())
  332|         self.end_entity("section")
  333|         for declaration in clas.declarations():
  334|             if isinstance(declaration, AST.Class):
  335|                 self.visitClass(declaration)
  336| 
  337|     def visitInheritance(self, inheritance):
  338|         self.write_entity("para", "Inherits "+Util.ccolonName(inheritance.parent().name(), self.scope()))
  339| 
  340| 
  341| def usage():
  342|     """Print usage to stdout"""
  343|     print \
  344| """
  345|   -v                         Verbose mode
  346|   -o <filename>              Output filename
  347|   -d                       Don't oupu
  348| 
  349| def __parseArgs(args):
  350|     global output, verbose, no_doctype, is_manual
  351|     output = sys.stdout
  352|     no_doctype = 0
  353|     is_manual = 0
  354|     try:
  355|         opts,remainder = getopt.getopt(args, "o:vdm")
  356|     except getopt.error, e:
  357|         sys.stderr.write("Error in arguments: " + str(e) + "\n")
  358|         sys.exit(1)
  359| 
  360|     for opt in opts:
  361|         o,a = opt
  362|         if o == "-o": output = open(a, "w")
  363|         elif o == "-v": verbose = 1
  364|         elif o == "-d": no_doctype = 1
  365|         elif o == "-m": is_manual = 1
  366| 
  367| def format(args, ast, config):
  368|     global output
  369|     __parseArgs(args)
  370|     if not no_doctype:
  371|         output.write("<!DOCTYPE classsynopsis PUBLIC \"-//OASIS//DTD DocBook V4.2//EN\">\n")
  372|     if is_manual:
  373|         formatter = DocFormatter(output)
  374|     else:
  375|         formatter = Formatter(output)
  376|     for d in ast.declarations():
  377|         d.accept(formatter)
  378| 
  379|     if output is not sys.stdout:
  380|         output.close()