#! /usr/bin/env python import sys, shlex, subprocess from optparse import OptionParser, OptionGroup from docutils.writers import latex2e try: import locale locale.setlocale(locale.LC_ALL, '') except: pass class AttributedDict(object): def __init__(self): object.__setattr__(self, "_items", dict()) def __getattr__(self, key): return self._items.get(key) def __delattr__(self, key): if key in self._items: del self._items[key] else: object.__delattr__(self, key) def __setattr__(self, key, val): if hasattr(self, key): object.__setattr__(self, key, val) else: self._items[key] = val parser = OptionParser(usage="Usage: %prog [options]") OPT = AttributedDict() OPT.common = OptionGroup(parser, "Common options") OPT.docinfo = OptionGroup(parser, "Documentation information") OPT.docinfo.add_option("-i", "--info", dest="docinfo", default=None, metavar="FILE", help="Read project information from FILE") def optparser(*groups): parser.add_option_group(OPT.common) [parser.add_option_group(g) for g in groups] return parser class DupRefsRemover(object): def __init__(self, out): self.out = out self.ref = dict() def handle_line(self, line): if line.startswith(".. _") and ":" in line: r = map(lambda x: x.strip(), line[4:].split(":", 1)) if r[0] in self.ref: # Do some sanity check if self.ref[r[0]] != r[1]: raise ValueError("Reference '%s' mismatch (%s - %s)" % (r[0], self.ref[r[0]], r[1])) else: # Add reference and print it out self.ref[r[0]] = r[1] self.out.write(line) else: self.out.write(line) class ShellEscapeRunner(object): def __init__(self, out): self.out = out def _expand(self, cmdline): cmd = shlex.split(cmdline, False) pipe = subprocess.Popen(cmd, stdout=subprocess.PIPE) return "\n ".join(pipe.communicate()[0].rstrip().splitlines()) def handle_line(self, line): index = line.find("!!") if index < 0: self.out.write(line) return self.out.write(line[:index].replace("\\!", "!")) cmd = line[index+2:] end = cmd.find("!!") if end > index: self.out.write(self._expand(cmd[:end].replace("\\!", "!"))) self.out.write(cmd[end+2:].replace("\\!", "!")) else: self.out.write(self._expand(cmd.replace("\\!", "!"))) self.out.write("\n") LATEX_SETTINGS = { "documentclass" : "scrbook", "documentoptions" : "11pt,oneside,a4paper", "table_style" : "booktabs", "use_latex_footnotes": 1, "use_latex_citations": 1, "use_latex_toc" : 1, "use_latex_docinfo" : 0, "use_latex_abstract" : 1, } EBOOK_SETTINGS = dict(LATEX_SETTINGS) EBOOK_SETTINGS["documentoptions"] = "14pt,oneside,b5paper" HTML_SETTINGS = { "field_name_limit" : 20, "cloak_email_addresses": 1, } # Those are two mocks needed to properly reuse the LaTeX code generator, but # avoiding generation of all the cruft from the preamble and the postamble, # and only the middle part. # class LaTeXStandaloneTranslator(latex2e.LaTeXTranslator): def __init__(self, document): self._class = document.settings.documentclass self._typearea = "" latex2e.LaTeXTranslator.__init__(self, document) # XXX We rely on DocumentClass having a document_class attribute and # on it not changing the attribute once initialization was done # if self.d_class.document_class == "igaliabk": self.d_class.document_class = "book" try: del self.head_prefix[self.head_prefix.index(self.typearea)] except AttributeError: pass def __get_typearea(self): if self._class == "igaliabk" or self._class.startswith("scr"): value = "" else: value = self._typearea return value def __set_typearea(self, value): self._typearea = value typarea = property(__get_typearea, __set_typearea) class LaTeXInsertTranslator(LaTeXStandaloneTranslator): def astext(self): return "".join(self.body) class LaTeXCustomWriter(latex2e.Writer): def __init__(self, translator_class): latex2e.Writer.__init__(self) self.translator_class = translator_class class DocTool(object): def main(self, argv=None): if argv is None: argv = sys.argv if len(argv) < 2: raise SystemExit("Insufficient number of arguments") method = getattr(self, "cmd_" + argv[1], self.no_command) method(*argv[1:]) def cmd_rstinsert(self, cmd, *args): from docutils.core import publish_cmdline, default_description description = ( "Generates LaTeX portions to be included in documents " + "from standalone reStructuredText sourtces " + default_description) publish_cmdline(writer=LaTeXCustomWriter(LaTeXInsertTranslator), description=description, argv=list(args) ) def cmd_rst2latex(self, cmd, *args): from docutils.core import publish_cmdline, default_description description = ( 'Generates LaTeX documents from RST sources. ' + default_description) publish_cmdline(writer=LaTeXCustomWriter(LaTeXStandaloneTranslator), settings_overrides=LATEX_SETTINGS, description=description, argv=list(args), ) def cmd_rst2ebook(self, cmd, *args): from docutils.core import publish_cmdline, default_description description = ( 'Generates LaTeX documents from RST sources. ' + default_description) publish_cmdline(writer=LaTeXCustomWriter(LaTeXStandaloneTranslator), settings_overrides=EBOOK_SETTINGS, description=description, argv=list(args), ) def cmd_rst2html(self, cmd, *args): from docutils.core import publish_cmdline, default_description description = ( 'Generates HTML documents from RST sources. ' + default_description) publish_cmdline(writer_name="html", settings_overrides=HTML_SETTINGS, description=description, argv=list(args), ) def cmd_toplevel(self, cmd, *args): opts, args = optparser(OPT.docinfo).parse_args(list(args)) if opts.docinfo: f = file(opts.docinfo, "rU") e = ShellEscapeRunner(sys.stdout) map(e.handle_line, f.readlines()) f.close() print print ".. contents::" print out = DupRefsRemover(sys.stdout) for name in args: if not name.endswith(".rst"): continue f = file(name, "rU") map(out.handle_line, filter(lambda l: ".. contents::" not in l, f.readlines()) ) f.close() print def cmd_htmlindex(self, cmd, *args): opts, args = optparser(OPT.docinfo).parse_args(list(args)) if opts.docinfo: f = file(opts.docinfo, "rU") e = ShellEscapeRunner(sys.stdout) map(e.handle_line, f.readlines()) f.close() print for name in args: # Skip non-RST inputs if not name.endswith(".rst"): continue f = file(name, "rU") line = f.readline().strip() f.close() print "#. `%s <%s.html>`__" % (line, name[:-4]) def cmd_help(self, cmd, *args): if args: self.main([args[0], args[0], "--help"]) else: print "Available commands:" for k in dir(self): if k.startswith("cmd_"): print " ", k[4:].replace("_", "-") cmd_commands = cmd_help def no_command(self, cmd, *args): raise SystemExit("No such command '%s'" % cmd) if __name__ == "__main__": DocTool().main()