00001
00002 """
00003 latex documentation tool
00004 """
00005
00006 __copyright__ = """
00007 Copyright 2008 Sean Ross-Ross
00008 """
00009 __license__ = """
00010 This file is part of SLIMpy .
00011
00012 SLIMpy is free software: you can redistribute it and/or modify
00013 it under the terms of the GNU Lesser General Public License as published by
00014 the Free Software Foundation, either version 3 of the License, or
00015 (at your option) any later version.
00016
00017 SLIMpy is distributed in the hope that it will be useful,
00018 but WITHOUT ANY WARRANTY; without even the implied warranty of
00019 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
00020 GNU Lesser General Public License for more details.
00021
00022 You should have received a copy of the GNU Lesser General Public License
00023 along with SLIMpy . If not, see <http://www.gnu.org/licenses/>.
00024 """
00025 import sys
00026 from pydoc import ( Doc, TextRepr, join , split, rstrip, classname,
00027 inspect, splitdoc, visiblename,
00028 isdata, strip, deque,
00029 _split_list, ispackage )
00030
00031 import __builtin__ , os, re
00032
00033 class TexDoc( Doc ):
00034 """Formatter class for text documentation."""
00035
00036
00037
00038 _repr_instance = TextRepr()
00039 repr = _repr_instance.repr
00040
00041 def bold( self, text ):
00042 """Format a string in bold by overstriking."""
00043 return "\\textbf{ %s }" %text
00044
00045 def italic( self, text ):
00046 """Format a string in bold by overstriking."""
00047 return "\\textit{ %s }" %text
00048
00049 def texttt( self, text ):
00050 return "\\texttt{ %s }" %text
00051
00052
00053
00054 def classdesc( self, classname, text ):
00055 classname= makeLatexSafe( classname )
00056
00057 return ( "\\section{\\module{%(classname)s}}\n"
00058 "\declaremodule{standard}{%(classname)s}{%(classname)s}\n"
00059 "\\begin{classdesc*} {%(classname)s}\n"
00060 "%(text)s\n"
00061 "\n\\end{classdesc*}" %vars() )
00062
00063 def funcdesc( self, name, parameters, text ):
00064 name= makeLatexSafe( name )
00065 parameters= makeLatexSafe( parameters )
00066
00067
00068
00069 parameters=parameters.replace( '(', '' )
00070 parameters=parameters.replace( ')', '' )
00071 parameters = parameters.split( ',' )
00072 parameters.remove( 'self' )
00073 parameters = ",".join( parameters )
00074
00075 return ( "\\begin{funcdesc} {%(name)s}{%(parameters)s}\n"
00076 "%(text)s\n"
00077 "\n\\end{funcdesc}" %vars() )
00078
00079 def itemize( self, List ):
00080 items = "\n\\item[] "+"\n\\item[] ".join( List )
00081
00082 return "\\begin{itemize}\n %(items)s\n\\end{itemize}" %vars()
00083
00084
00085 def indent( self, text, prefix=' ' ):
00086 """Indent text by prepending a given prefix to each line."""
00087 if not text: return ''
00088 lines = split( text, '\n' )
00089 lines = map( lambda line, prefix=prefix: prefix + line, lines )
00090 if lines: lines[-1] = rstrip( lines[-1] )
00091 return join( lines, '\n' )
00092
00093 def section( self, title, contents ):
00094 """Format a section with a given heading."""
00095 return "\\section{ " + title + '}\n' + rstrip( self.indent( contents ) ) + '\n'
00096
00097 def paragraph( self, title, contents ):
00098 """Format a section with a given heading."""
00099 return "\\paragraph{ " + title + '}\n' + rstrip( self.indent( contents ) ) + '\n'
00100
00101
00102
00103 def formattree( self, tree, modname, parent=None, prefix='' ):
00104 """Render in text a class tree as returned by inspect.getclasstree()."""
00105 result = ''
00106 for entry in tree:
00107 if type( entry ) is type( () ):
00108 c, bases = entry
00109 result = result + prefix + classname( c, modname )
00110 if bases and bases != ( parent, ):
00111 parents = map( lambda c, m=modname: classname( c, m ), bases )
00112 result = result + '(%s)' % join( parents, ', ' )
00113 result = result + '\n'
00114 elif type( entry ) is type( [] ):
00115 result = result + self.formattree(
00116 entry, modname, c, prefix + ' ' )
00117 return result
00118
00119 def docmodule( self, object, name=None, mod=None ):
00120 """Produce text documentation for a given module object."""
00121 name = object.__name__
00122 synop, desc = splitdoc( getdoc( object ) )
00123 result = self.section( name, synop )
00124
00125 try:
00126 all = object.__all__
00127 except AttributeError:
00128 all = None
00129
00130 try:
00131 file = inspect.getabsfile( object )
00132 except TypeError:
00133 file = '(built-in)'
00134 result = result + self.paragraph( 'File', file )
00135
00136 if desc:
00137 result = result + self.section( 'Description', desc )
00138
00139 classes = []
00140 for key, value in inspect.getmembers( object, inspect.isclass ):
00141
00142 if ( all is not None
00143 or ( inspect.getmodule( value ) or object ) is object ):
00144 if visiblename( key, all ):
00145 classes.append( ( key, value ) )
00146 funcs = []
00147 for key, value in inspect.getmembers( object, inspect.isroutine ):
00148
00149 if ( all is not None or
00150 inspect.isbuiltin( value ) or inspect.getmodule( value ) is object ):
00151 if visiblename( key, all ):
00152 funcs.append( ( key, value ) )
00153 data = []
00154 for key, value in inspect.getmembers( object, isdata ):
00155 if visiblename( key, all ):
00156 data.append( ( key, value ) )
00157
00158 if hasattr( object, '__path__' ):
00159 modpkgs = []
00160 for file in os.listdir( object.__path__[0] ):
00161 path = os.path.join( object.__path__[0], file )
00162 modname = inspect.getmodulename( file )
00163 if modname != '__init__':
00164 if modname and modname not in modpkgs:
00165 modpkgs.append( modname )
00166 elif ispackage( path ):
00167 modpkgs.append( file + ' (package)' )
00168 modpkgs.sort()
00169 result = result + self.section(
00170 'Package Contents', join( modpkgs, '\n' ) )
00171
00172 if classes:
00173 classlist = map( lambda ( key, value ): value, classes )
00174 contents = []
00175 for key, value in classes:
00176 contents.append( self.document( value, key, name ) )
00177 result = result + self.paragraph( 'Classes', "\n\\begin{itemize}\n\\item[] "+join( contents, '\n\\item[] ' ) +'\n\\end{itemize}' )
00178
00179 if funcs:
00180 contents = []
00181 for key, value in funcs:
00182 contents.append( self.document( value, key, name ) )
00183 result = result + self.paragraph( 'Functions', join( contents, '\n' ) )
00184
00185 if data:
00186 contents = []
00187 for key, value in data:
00188 contents.append( self.docother( value, key, name, 70 ) )
00189 result = result + self.paragraph( 'Data', join( contents, '\n' ) )
00190
00191 if hasattr( object, '__version__' ):
00192 version = str( object.__version__ )
00193 if version[:11] == '$' + 'Revision: ' and version[-1:] == '$':
00194 version = strip( version[11:-1] )
00195 result = result + self.paragraph( 'Version', version )
00196 if hasattr( object, '__date__' ):
00197 result = result + self.paragraph( 'Date', str( object.__date__ ) )
00198 if hasattr( object, '__author__' ):
00199 result = result + self.paragraph( 'Author', str( object.__author__ ) )
00200 if hasattr( object, '__credits__' ):
00201 result = result + self.paragraph( 'Credits', str( object.__credits__ ) )
00202 return result
00203
00204 def docclass( self, object, name=None, mod=None ):
00205 """Produce tex documentation for a given class object."""
00206 realname = object.__name__
00207 name = name or realname
00208 bases = object.__bases__
00209
00210 def makename( c, m=object.__module__ ):
00211 return classname( c, m )
00212
00213 if name == realname:
00214 title = self.texttt( 'class ' + realname )
00215 else:
00216 title = self.texttt( name + ' = class ' + realname )
00217 if bases:
00218 parents = map( makename, bases )
00219 title = title + '(%s)' % join( parents, ', ' )
00220
00221 doc = getdoc( object )
00222 contents = doc and [doc + '\n'] or []
00223 push = contents.append
00224
00225
00226 mro = deque( inspect.getmro( object ) )
00227
00228
00229
00230
00231
00232
00233
00234 class HorizontalRule:
00235 def __init__( self ):
00236 self.needone = 0
00237 def maybe( self ):
00238 if self.needone:
00239 push( '%' * 40 )
00240 self.needone = 1
00241 hr = HorizontalRule()
00242
00243 def spill( msg, attrs, predicate ):
00244 ok, attrs = _split_list( attrs, predicate )
00245 if ok:
00246 hr.maybe()
00247
00248 docstr = []
00249 for name, kind, homecls, value in ok:
00250 if name.startswith( '__' ) and name.endswith( '__' ):
00251 pass
00252 else:
00253 docstr.append( self.document( getattr( object, name ),
00254 name, mod, object ) )
00255 push( "\n".join( docstr ) )
00256
00257 return attrs
00258
00259 def spillproperties( msg, attrs, predicate ):
00260 ok, attrs = _split_list( attrs, predicate )
00261 if ok:
00262 hr.maybe()
00263 push( msg )
00264 for name, kind, homecls, value in ok:
00265 push( self._docproperty( name, value, mod ) )
00266 return attrs
00267
00268 def spilldata( msg, attrs, predicate ):
00269 ok, attrs = _split_list( attrs, predicate )
00270 if ok:
00271 hr.maybe()
00272 push( msg )
00273 for name, kind, homecls, value in ok:
00274 if callable( value ) or inspect.isdatadescriptor( value ):
00275 doc = getdoc( value )
00276 else:
00277 doc = None
00278 push( self.docother( getattr( object, name ),
00279 name, mod, 70, doc ) + '\n' )
00280 return attrs
00281
00282 attrs = filter( lambda ( name, kind, cls, value ): visiblename( name ),
00283 inspect.classify_class_attrs( object ) )
00284 while attrs:
00285 if mro:
00286 thisclass = mro.popleft()
00287 else:
00288 thisclass = attrs[0][2]
00289 attrs, inherited = _split_list( attrs, lambda t: t[2] is thisclass )
00290
00291 if thisclass is __builtin__.object:
00292 attrs = inherited
00293 continue
00294 elif thisclass is object:
00295 tag = "defined here"
00296 else:
00297 tag = "inherited from %s" % classname( thisclass,
00298 object.__module__ )
00299 filter( lambda t: not t[0].startswith( '_' ), attrs )
00300
00301
00302 attrs.sort()
00303
00304
00305 attrs = spill( "Methods %s:\n" % tag, attrs,
00306 lambda t: t[1] == 'method' )
00307 attrs = spill( "Class methods %s:\n" % tag, attrs,
00308 lambda t: t[1] == 'class method' )
00309 attrs = spill( "Static methods %s:\n" % tag, attrs,
00310 lambda t: t[1] == 'static method' )
00311
00312
00313
00314
00315
00316
00317
00318 contents = '\n'.join( contents )
00319 if not contents:
00320 return title + '\n'
00321 return self.classdesc( realname, '\n' + self.indent( rstrip( contents ), ' ' ) )
00322
00323 def formatvalue( self, object ):
00324 """Format an argument default value as text."""
00325 return '=' + self.repr( object )
00326
00327 def docroutine( self, object, name=None, mod=None, cl=None ):
00328 """Produce text documentation for a function or method object."""
00329 realname = object.__name__
00330 name = name or realname
00331 note = ''
00332 skipdocs = 0
00333 if inspect.ismethod( object ):
00334 imclass = object.im_class
00335 if cl:
00336 if imclass is not cl:
00337 note = ' from ' + classname( imclass, mod )
00338 else:
00339 if object.im_self:
00340 note = ' method of %s instance' % classname(
00341 object.im_self.__class__, mod )
00342 else:
00343 note = ' unbound %s method' % classname( imclass, mod )
00344 object = object.im_func
00345
00346 if name == realname:
00347 title = realname
00348 else:
00349 if ( cl and realname in cl.__dict__ and
00350 cl.__dict__[realname] is object ):
00351 skipdocs = 1
00352 title = name + ' = ' + realname
00353 if inspect.isfunction( object ):
00354 args, varargs, varkw, defaults = inspect.getargspec( object )
00355 argspec = inspect.formatargspec(
00356 args, varargs, varkw, defaults, formatvalue=self.formatvalue )
00357 if realname == '<lambda>':
00358 title = 'lambda'
00359 argspec = argspec[1:-1]
00360 else:
00361 argspec = '(...)'
00362
00363
00364
00365 if skipdocs:
00366 text = '\n'
00367 else:
00368 doc = getdoc( object ) or ''
00369 text= ( doc and rstrip( self.indent( doc ) ) + '\n' )
00370
00371 return self.funcdesc( title, argspec, text )
00372
00373 def _docproperty( self, name, value, mod ):
00374 results = []
00375 push = results.append
00376
00377 if name:
00378 push( name )
00379 need_blank_after_doc = 0
00380 doc = getdoc( value ) or ''
00381 if doc:
00382 push( self.indent( doc ) )
00383 need_blank_after_doc = 1
00384 for attr, tag in [( 'fget', '<get>' ),
00385 ( 'fset', '<set>' ),
00386 ( 'fdel', '<delete>' )]:
00387 func = getattr( value, attr )
00388 if func is not None:
00389 if need_blank_after_doc:
00390 push( '' )
00391 need_blank_after_doc = 0
00392 base = self.document( func, tag, mod )
00393 push( self.indent( base ) )
00394
00395 return '\n'.join( results )
00396
00397 def docproperty( self, object, name=None, mod=None, cl=None ):
00398 """Produce text documentation for a property."""
00399 return self._docproperty( name, object, mod )
00400
00401 def docother( self, object, name=None, mod=None, maxlen=None, doc=None ):
00402 """Produce text documentation for a data object."""
00403 repr = self.repr( object )
00404 if maxlen:
00405 line = ( name and name + ' = ' or '' ) + repr
00406 chop = maxlen - len( line )
00407 if chop < 0: repr = repr[:chop] + '...'
00408 line = ( name and self.bold( name ) + ' = ' or '' ) + repr
00409 if doc is not None:
00410 line += '\n' + self.indent( str( doc ) )
00411 return line
00412
00413 def getdoc( object ):
00414 """Get the doc string or comments for an object."""
00415 result = inspect.getdoc( object ) or inspect.getcomments( object )
00416 res = result and re.sub( '^ *\n', '', rstrip( result ) ) or ''
00417
00418 return makeLatexSafe( res )
00419
00420 def makeLatexSafe( text ):
00421 texmap = [( '\\', '\\e ' ), ( '{', '\\{' ), ( '}', '\\}' ), ( '_', '{\\_}' )]
00422
00423 if '--latex' in text:
00424 return text
00425 else:
00426 for key, val in texmap:
00427 text = text.replace( key, val )
00428 return text
00429
00430
00431 latex = TexDoc()
00432
00433
00434 if __name__ == "__main__":
00435 if len( sys.argv ) == 1:
00436 print 'usage python texDoc.py moule class'
00437 else:
00438 item = sys.argv[1]
00439 From = sys.argv[2]
00440
00441 x = __import__( item, globals(), locals(), From )
00442
00443 k = getattr( x, From )
00444
00445 print latex.document( k )
00446