00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012 import sys, os, re, shutil
00013 import subprocess, signal
00014
00015 from wikidoc import wikidoc
00016 from formatter import Formatter
00017 from optparse import OptionParser
00018
00019 excludeRegexList = []
00020 format = 'html'
00021 verb = 0
00022 sitexml = ""
00023
00024 usage = """usage: %prog [options] <basedir> <docdir>
00025
00026 Takes all .m files in basedir and its subdirectories and converts
00027 them to html documentation, placing the results in docdir."""
00028
00029 parser = OptionParser(usage=usage)
00030
00031 parser.add_option(
00032 "-f", "--format",
00033 dest = "format",
00034 default = "html",
00035 action = "store",
00036 help = "specify the output format (html, wiki, web)",
00037 metavar = "STRING")
00038
00039 parser.add_option(
00040 "-x", "--exclude",
00041 dest = "excludeList",
00042 action = "append",
00043 type = "string",
00044 help = "exclude files matching the specified regexp")
00045
00046 parser.add_option(
00047 "-v", "--verbose",
00048 dest = "verb",
00049 default = False,
00050 action = "store_true",
00051 help = "print debug information")
00052
00053 parser.add_option(
00054 "-t", "--helptoc",
00055 dest = "helptoc",
00056 default = False,
00057 action = "store_true",
00058 help = "create helptoc.xml")
00059
00060 parser.add_option(
00061 "", "--helptoc-toolbox-name",
00062 dest = "helptoc_toolbox_name",
00063 default = "Example",
00064 action = "store",
00065 type = "string",
00066 help = "helptoc.xml: Toolbox Name")
00067
00068
00069 def runcmd(cmd):
00070
00071 """
00072 runcmd(CMD) runs the command CMD. The function waits for the
00073 command to complete and correctly react to Ctrl-C by stopping the
00074 process and raising an exception.
00075 """
00076 try:
00077 p = subprocess.Popen(cmd, shell=True)
00078 sts = os.waitpid(p.pid, 0)
00079 except (KeyboardInterrupt, SystemExit):
00080 os.kill(p.pid, signal.SIGKILL)
00081 raise
00082
00083
00084 class MFile:
00085
00086 """
00087 MFile('sub/file.m') represents a MATLAB M-File.
00088 """
00089 def __init__(self, basedir, dirname, name):
00090 funcname = os.path.splitext(name)[0]
00091
00092 self.funcname = funcname
00093 self.path = os.path.join(basedir, dirname, name)
00094 self.mdocname = funcname.replace(os.path.sep, '_')
00095 self.webname = funcname.replace(os.path.sep, '.')
00096 self.htmlname = self.mdocname + '.html'
00097 self.wikiname = 'MDoc_' + (os.path.join(dirname, funcname)
00098 .upper().replace(os.path.sep, '_'))
00099
00100 self.prev = None
00101 self.next = None
00102 self.node = None
00103
00104 def getId (self, format='html'):
00105 if format == 'html':
00106 return self.htmlname
00107 elif format == 'web':
00108 return self.webname
00109 elif format == 'wiki':
00110 return self.wikiname
00111
00112 def getRef (self, format='html'):
00113 if format == 'html':
00114 return self.htmlname
00115 elif format == 'web':
00116 return '%pathto:' + self.webname + ';'
00117 elif format == 'wiki':
00118 return self.wikiname
00119
00120 def __cmp__(self, other):
00121 return cmp(self.webname, other.webname)
00122
00123 def __str__(self):
00124 str = "MFile: %s\n" % (self.funcname)
00125 str += " path : %s\n" % (self.path)
00126 str += " mdocname: %s\n" % (self.mdocname)
00127 str += " htmlname: %s\n" % (self.htmlname)
00128 str += " wikiname: %s\n" % (self.wikiname)
00129 return str
00130
00131
00132 class Node:
00133
00134 """
00135 A Node N represents a node in the toolbox hierechy. A node is a
00136 directory in the toolbox hierarchy and contains both M-files and
00137 other sub-directories.
00138 """
00139 def __init__(self, dirname):
00140 self.dirname = dirname
00141 self.children = []
00142 self.mfiles = []
00143
00144 def addChildNode(self, node):
00145 "Add a child node (toolbox subdirectory) to this node"
00146 self.children.append(node)
00147
00148 def addMFile(self, mfile):
00149 "Add a MATLAB M-File to this node"
00150 self.mfiles.append(mfile)
00151 mfile.node = self
00152
00153 def toIndexPage(self, format='html', depth=1):
00154 "Converts the node hierarchy rooted here into an index."
00155 page = ""
00156 if format == 'html' or format == 'web':
00157 if len(self.mfiles) > 0:
00158 page += "<b>%s</b>" % (self.dirname.upper())
00159 page += "<ul>\n"
00160 for m in self.mfiles:
00161 page += "<li>"
00162 page += "<b><a href='%s'>%s</a></b>" % (m.getRef(format),
00163 m.funcname)
00164 page += " %s" % (m.brief)
00165 page += "</li>"
00166 page += "</ul>\n"
00167 elif format == 'wiki':
00168 if len(self.mfiles) > 0:
00169 if depth > 1:
00170 page += "=== %s ===\n" % (self.dirname.upper())
00171 for m in self.mfiles:
00172 page += "* [[%s|%s]]" % (m.getRef(format), m.funcname)
00173 page += " %s\n" % (m.brief)
00174 elif format == 'helptoc':
00175 for m in self.mfiles:
00176 page += "<tocitem target='%s'>%s</tocitem>\n" % (m.getRef('html'),
00177 m.funcname)
00178 else:
00179 assert False
00180 for n in self.children:
00181 page += n.toIndexPage(format, depth+1)
00182 return page
00183
00184 def toIndexXML(self):
00185 xml = ""
00186 for m in self.mfiles:
00187 dirname = m.node.dirname.upper()
00188 if len(dirname) > 0:
00189 xml += \
00190 "<page id='%s' name='%s' title='%s - %s' hide='yes'>" \
00191 "<div class='mdoc'>" \
00192 "<include src='%s'/></div></page>\n" % (m.getId('web'), m.funcname,
00193 dirname,
00194 m.funcname, m.htmlname)
00195 else:
00196 xml += \
00197 "<page id='%s' name='%s' title='%s' hide='yes'>" \
00198 "<div class='mdoc'>" \
00199 "<include src='%s'/></div></page>\n" % (m.getId('web'), m.funcname,
00200 m.funcname, m.htmlname)
00201
00202 for n in self.children:
00203 xml += n.toIndexXML() ;
00204 return xml
00205
00206 def __str__(self):
00207 s = "Node: %s\n" % self.dirname
00208 for m in self.mfiles:
00209 s += m.__str__()
00210 for n in self.children:
00211 s += n.__str__()
00212 return s
00213
00214
00215 def depth_first(node):
00216
00217 """
00218 depth_first(NODE) is a generator that implements a depth first
00219 visit of the node hierarchy rooted at NODE.
00220 """
00221 yield node
00222 for n in node.children:
00223 for m in depth_first(n):
00224 yield m
00225 return
00226
00227
00228 def extract(path):
00229
00230 """
00231 (BODY, FUNC, BRIEF) = extract(PATH) extracts the comment BODY, the
00232 function name FUNC and the brief description BRIEF from the MATLAB
00233 M-file located at PATH.
00234 """
00235 body = []
00236 func = ""
00237 brief = ""
00238 seenfunction = False
00239 seenpercent = False
00240
00241 for l in open(path):
00242
00243
00244 line = l.strip().lstrip()
00245
00246 if line.startswith('%'): seenpercent = True
00247 if line.startswith('function'):
00248 seenfunction = True
00249 continue
00250 if not line.startswith('%'):
00251 if (seenfunction and seenpercent) or not seenfunction:
00252 break
00253 else:
00254 continue
00255
00256
00257 line = line[1:]
00258 body.append('%s\n' % line)
00259
00260
00261 if len(body) > 0:
00262 head = body[0]
00263 body = body[1:]
00264 match = re.match(r"^\s*(\w+)\s*(\S.*)\n$", head)
00265 func = match.group(1)
00266 brief = match.group(2)
00267
00268 return (body, func, brief)
00269
00270
00271
00272 def xscan(baseDir, subDir=''):
00273
00274 """
00275 NODE = xscan(BASEDIR) recusrively scans the directory BASEDIR and
00276 construct the toolbox hierarchy rooted at NODE.
00277 """
00278
00279 node = Node(subDir)
00280 dir = os.listdir(os.path.join(baseDir, subDir))
00281 fileNames = [f for f in dir if os.path.isfile(
00282 os.path.join(baseDir, subDir, f))]
00283 subSubDirs = [s for s in dir if os.path.isdir (
00284 os.path.join(baseDir, subDir, s))]
00285 fileNames.sort()
00286
00287
00288 for fileName in fileNames:
00289
00290 if not os.path.splitext(fileName)[1] == '.m':
00291 continue
00292
00293
00294 exclude = False
00295 for rx in excludeRegexList:
00296 fileRelPath = os.path.join(subDir, fileName)
00297 mo = rx.match(fileRelPath)
00298 if mo and (mo.end() - mo.start() == len(fileRelPath)):
00299 if verb:
00300 print "mdoc: excluding ''%s''." % fileRelPath
00301 exclude = True
00302 if exclude: continue
00303
00304 node.addMFile(MFile(baseDir, subDir, fileName))
00305
00306
00307 for s in subSubDirs:
00308 node.addChildNode(xscan(basedir, os.path.join(subDir, s)))
00309
00310 return node
00311
00312
00313 def breadCrumb(m):
00314
00315 breadcrumb = "<ul class='breadcrumb'>"
00316 if format == 'web':
00317 breadcrumb += "<li><a href='%pathto:matlab;'>Index</a></li>"
00318 else:
00319 breadcrumb += "<li><a href='index.html'>Index</a></li>"
00320 if m.prev: breadcrumb += "<li><a href='%s'>Prev</a></li>" % m.prev.getRef(format)
00321 if m.next: breadcrumb += "<li><a href='%s'>Next</a></li>" % m.next.getRef(format)
00322 breadcrumb += "</ul>"
00323
00324
00325 return breadcrumb
00326
00327
00328 if __name__ == '__main__':
00329
00330
00331
00332
00333
00334
00335 (options, args) = parser.parse_args()
00336
00337 if options.verb: verb = 1
00338 format = options.format
00339 helptoc = options.helptoc
00340
00341 print options.excludeList
00342 for ex in options.excludeList:
00343 rx = re.compile(ex)
00344 excludeRegexList.append(rx)
00345
00346 if len(args) != 2:
00347 parser.print_help()
00348 sys.exit(2)
00349
00350 basedir = args[0]
00351 docdir = args[1]
00352
00353 if not basedir.endswith('/'): basedir = basedir + "/"
00354 if not basedir.endswith('/'): docdir = docdir + "/"
00355
00356 if verb:
00357 print "mdoc: search path: %s" % basedir
00358 print "mdoc: output path: %s" % docdir
00359 print "mdoc: output format: %s" % format
00360
00361
00362
00363
00364
00365 toolbox = xscan(basedir)
00366
00367
00368
00369
00370
00371 linkdict = {}
00372 mfiles = {}
00373 prev = None
00374 next = None
00375 for n in depth_first(toolbox):
00376 for m in n.mfiles:
00377 if prev:
00378 prev.next = m
00379 m.prev = prev
00380 prev = m
00381 func = m.funcname.upper()
00382 mfiles[func] = m
00383 linkdict[func] = m.getRef(format)
00384 if verb:
00385 print "mdoc: num mfiles: %d" % (len(mfiles))
00386
00387
00388 if not os.access(docdir, os.F_OK):
00389 os.makedirs(docdir)
00390
00391
00392
00393
00394 for (func, m) in mfiles.items():
00395
00396 if format == 'wiki':
00397 outname = m.wikiname
00398 elif format == 'html':
00399 outname = m.htmlname
00400 elif format == 'web':
00401 outname = m.htmlname
00402
00403 if verb:
00404 print "mdoc: generating %s from %s" % (outname, m.path)
00405
00406
00407 (lines, func, brief) = extract(m.path)
00408
00409 m.brief = brief
00410
00411
00412 content = ""
00413 if len(lines) > 0:
00414 if format == 'wiki' :
00415 formatter = Formatter(lines, linkdict, 'wiki')
00416 else:
00417 formatter = Formatter(lines, linkdict, 'a')
00418
00419 content = formatter.toDOM().toxml("UTF-8")
00420 content = content[content.find('?>')+2:]
00421
00422
00423 if not format == 'wiki':
00424 content = breadCrumb(m) + content
00425
00426 if format == 'web':
00427 content = "<group>\n" + content + "</group>\n"
00428
00429
00430 if format == 'wiki':
00431 f = open(os.path.join(docdir, m.wikiname), 'w')
00432 else:
00433 f = open(os.path.join(docdir, m.htmlname), 'w')
00434 f.write(content)
00435 f.close()
00436
00437
00438
00439
00440
00441 page = ""
00442 if format == 'html':
00443 pagename = 'index.html'
00444 page += toolbox.toIndexPage('html')
00445 elif format == 'web':
00446 pagename = 'mdoc.html'
00447 page += '<group>\n' + toolbox.toIndexPage('web') + '</group>\n'
00448 elif format =='wiki' :
00449 pagename = 'MDoc'
00450 page = "== Documentation ==\n"
00451 page += toolbox.toIndexPage('wiki')
00452
00453 f = open(os.path.join(docdir, pagename), 'w')
00454 f.write(page)
00455 f.close()
00456
00457 if format == 'web':
00458 f = open(os.path.join(docdir, "mdoc.xml"), 'w')
00459 f.write("<group>"+toolbox.toIndexXML()+"</group>\n")
00460 f.close()
00461
00462
00463
00464
00465
00466 if helptoc:
00467 page = """<?xml version='1.0' encoding="utf-8"?>
00468 <toc version="2.0">
00469 <tocitem target="../index.html">%s
00470 <tocitem target="%s" image="HelpIcon.FUNCTION">Functions
00471 """ % (options.helptoc_toolbox_name, pagename)
00472 page += toolbox.toIndexPage('helptoc')
00473 page += """
00474 </tocitem>
00475 </tocitem>
00476 </toc>
00477 """
00478 f = open(os.path.join(docdir, "helptoc.xml"), 'w')
00479 f.write(page)
00480 f.close()
00481
00482
00483
00484
00485 def towiki(docdir, pagename):
00486 pagenamewiki = pagename + '.wiki'
00487 runcmd("cd %s ; mvs update %s" % (docdir, pagenamewiki))
00488 if verb:
00489 print "mdoc: converting", pagename, "to", pagenamewiki
00490 wikidoc(os.path.join(docdir, pagenamewiki),
00491 os.path.join(docdir, pagename))
00492 runcmd("cd %s ; mvs commit -M -m 'Documentation update' %s" % (docdir, pagenamewiki))
00493
00494 if format == 'wiki' :
00495 try:
00496 towiki(docdir, pagename)
00497 except (KeyboardInterrupt, SystemExit):
00498 sys.exit(1)
00499
00500 for (func, m) in mfiles.items():
00501 try:
00502 towiki(docdir, m.wikiname)
00503 except (KeyboardInterrupt, SystemExit):
00504 sys.exit(1)