Mixing tabs and spaces will make newer versions of Python to complain about indentation -- and by the way with good reason as it usually introduces subtle bugs. Therefore, make all the code use spaces.
295 lines
8.3 KiB
Python
Executable file
295 lines
8.3 KiB
Python
Executable file
#! /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 <command> [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()
|
|
|