# -*- coding: utf-8 -*-
# cython: language_level=3, always_allow_keywords=True
## Copyright 1999-2024 by LivingLogic AG, Bayreuth/Germany
## Copyright 1999-2024 by Walter Dörwald
##
## All Rights Reserved
##
## See ll/xist/__init__.py for the license
"""
This namespace module provides classes that can be used for generating
documentation (in HTML, DocBook and XSL-FO).
"""
import sys, types, inspect, textwrap, warnings, operator
import ll
from ll.xist import xsc, parse, sims, xfind
from ll.xist.ns import html, docbook, fo, specials, xml
__docformat__ = "reStructuredText"
xmlns = "http://xmlns.livinglogic.de/xist/ns/doc"
def _getmodulename(thing):
module = inspect.getmodule(thing)
if module is None:
return "???"
else:
return module.__name__
[docs]
def getdoc(thing, format):
"""
Return the docstring for ``thing``, as an XIST node using this namespace module.
``format`` specifies how to treat the docstring:
``"plaintext"``
Treat to docstring as text. This returns a single :class:`~ll.xist.xsc.Text`
node.
``"restructuredtext"``
This interprets the docstring as ReST source and converts it to use
this namespace.
``"xist"``
This treats the docstring as XML, which will be parsed using this
namespace as the default namespace.
"""
if thing.__doc__ is None:
return xsc.Null
# Remove indentation
lines = textwrap.dedent(thing.__doc__).split("\n")
# remove empty lines
while lines and not lines[0]:
del lines[0]
while lines and not lines[-1]:
del lines[-1]
text = "\n".join(lines)
modulename = _getmodulename(thing)
if inspect.ismethod(thing):
base = f"METHOD-DOCSTRING({modulename}.{thing.__class__.__name__}.{thing.__qualname__})"
elif isinstance(thing, property):
base = f"PROPERTY-DOCSTRING({modulename}.{thing})"
elif inspect.isfunction(thing):
base = f"FUNCTION-DOCSTRING({modulename}.{thing.__qualname__})"
elif inspect.isclass(thing):
base = f"CLASS-DOCSTRING({modulename}.{thing.__qualname__})"
elif inspect.ismodule(thing):
base = f"MODULE-DOCSTRING({modulename})"
else:
base = "DOCSTRING"
lformat = format.lower()
if lformat == "plaintext":
return xsc.Text(text)
elif lformat == "restructuredtext":
from ll.xist.ns import rest, doc
return rest.fromstring(text, base=base).conv(target=doc)
elif lformat == "xist":
from ll.xist.ns import doc
node = parse.tree(parse.String(text), parse.SGMLOP(), parse.NS(doc), parse.Node(pool=xsc.docpool(), base=base))
if not node[p]: # optimization: one paragraph docstrings don't need a <p> element.
node = p(node)
if inspect.ismethod(thing):
# Use the original method instead of the decorator
realthing = thing
while hasattr(realthing, "__wrapped__"):
realthing = realthing.__wrapped__
for ref in node.walknodes(pyref):
if "module" not in ref.attrs:
ref["module"] = _getmodulename(realthing)
if "class_" not in ref.attrs:
ref["class_"] = thing.__self__.__class__.__name__
if "method" not in ref.attrs:
ref["method"] = thing.__name__
elif inspect.isfunction(thing):
# Use the original method instead of the decorator
while hasattr(thing, "__wrapped__"):
thing = thing.__wrapped__
for ref in node.walknodes(pyref):
if "module" not in ref.attrs:
ref["module"] = _getmodulename(thing)
elif inspect.isclass(thing):
for ref in node.walknodes(pyref):
if "module" not in ref.attrs:
ref["module"] = _getmodulename(thing)
if "class_" not in ref.attrs:
ref["class_"] = thing.__name__
elif inspect.ismodule(thing):
for ref in node.walknodes(pyref):
if "module" not in ref.attrs:
ref["module"] = thing.__name__
return node
else:
raise ValueError(f"unsupported __docformat__ {format!r}")
def getsourceline(obj):
if isinstance(obj, property):
pos = 999999999
if obj.fget is not None:
pos = min(pos, obj.fget.__code__.co_firstlineno)
if obj.fset is not None:
pos = min(pos, obj.fset.__code__.co_firstlineno)
if obj.fdel is not None:
pos = min(pos, obj.fdel.__code__.co_firstlineno)
else:
while hasattr(obj, "__wrapped__"):
obj = obj.__wrapped__
try: # FIXME: Python SF bug #1224621
pos = inspect.getsourcelines(obj)[-1]
except IndentationError:
pos = 42
return pos
def _namekey(obj, name):
return (getsourceline(obj), name or obj.__name__)
def _codeheader(thing, name, type):
spec = inspect.getfullargspec(thing)
sig = xsc.Frag()
offset = len(spec.args)
if spec.defaults is not None:
offset -= len(spec.defaults)
for (i, arg) in enumerate(spec.args):
if i == 0:
if issubclass(type, meth):
if arg == "self":
sig.append(obj(self()))
elif arg == "cls":
sig.append(obj(cls()))
else:
sig.append(obj(arg))
else:
sig.append(obj(arg))
else:
if sig:
sig.append(", ")
sig.append(obj(arg))
if i >= offset:
sig.append("=", lit(repr(spec.defaults[i-offset])))
if spec.varargs:
if sig:
sig.append(", ")
sig.append("*", obj(spec.varargs))
if spec.varkw:
if sig:
sig.append(", ")
sig.append("**", obj(spec.varkw))
for (i, arg) in enumerate(spec.kwonlyargs):
if sig:
sig.append(", ")
sig.append(obj(arg))
if arg in spec.kwonlydefaults:
sig.append("=", lit(repr(spec.kwonlydefaults[arg])))
sig.insert(0, type(name), "\u200b(") # use "ZERO WIDTH SPACE" to allow linebreaks
sig.append(")")
return sig
class _stack:
def __init__(self, context, obj):
self.context = context
self.obj = obj
def __enter__(self):
self.context.stack.append(self.obj)
def __exit__(self, type, value, traceback):
self.context.stack.pop()
[docs]
def explain(thing, name=None, format=None, context=[]):
"""
Return a XML representation of the doc string of ``thing``, which can be
a function, method, class or module.
If ``thing`` is not a module, you must pass the context in ``context``,
i.e. a list of names of objects into which ``thing`` is nested. This
means the first entry will always be the module name, and the other entries
will be class names.
For the meaning for ``format`` see, :func:`getdoc`.
"""
def _append(all, obj, varname):
try:
all.append((_namekey(obj, varname), obj, varname))
except (IOError, TypeError):
pass
# Determine visibility
visibility = "public"
testname = name or thing.__name__
if testname.startswith("_"):
visibility = "protected"
if testname.startswith("__"):
visibility = "private"
if testname.endswith("__"):
visibility = "special"
# Determine whether ``thing`` has a docstring
if format is None and inspect.ismodule(thing):
format = getattr(thing, "__docformat__", "plaintext").split()[0]
doc = getdoc(thing, format)
if doc is xsc.Null:
hasdoc = "nodoc"
else:
hasdoc = "doc"
# Determine type
if inspect.ismethod(thing):
name = name or thing.__name__
context = context + [(thing, name)]
id = "-".join(info[1] for info in context[1:]) or None
sig = xsc.Frag()
if name != thing.__name__ and not (thing.__name__.startswith("__") and name=="_" + thing.__self__.__class__.__name__ + thing.__name__):
sig.append(meth(name), " = ")
sig.append("def ", _codeheader(thing.__func__, thing.__name__, meth), ":")
return section(h(sig), doc, role=(visibility, " method ", hasdoc), id=id)
elif inspect.isfunction(thing):
name = name or thing.__name__
context = context + [(thing, name)]
id = "-".join(info[1] for info in context[1:]) or None
sig = xsc.Frag(
"def ",
_codeheader(thing, name, func),
":"
)
return section(h(sig), doc, role=(visibility, " function ", hasdoc), id=id)
elif isinstance(thing, property):
context = context + [(thing, name)]
id = "-".join(info[1] for info in context[1:]) or None
sig = xsc.Frag(
"property ", name, ":"
)
node = section(h(sig), doc, role=(visibility, " property ", hasdoc), id=id)
if thing.fget is not None:
node.append(explain(thing.fget, "__get__", format, context))
if thing.fset is not None:
node.append(explain(thing.fset, "__set__", format, context))
if thing.fdel is not None:
node.append(explain(thing.fdel, "__delete__", format, context))
return node
elif inspect.isclass(thing):
name = name or thing.__name__
context = context + [(thing, name)]
id = "-".join(info[1] for info in context[1:]) or None
bases = xsc.Frag()
if len(thing.__bases__):
for baseclass in thing.__bases__:
if baseclass.__module__ in ("builtins", "exceptions"):
ref = class_(baseclass.__name__)
else:
try:
baseclassname = baseclass.__qualname__
except AttributeError:
baseclassname = baseclass.__name__
if thing.__module__ != baseclass.__module__:
baseclassname4text = baseclass.__module__ + "." + baseclassname
else:
baseclassname4text = baseclassname
#baseclassname4text = u".\u200b".join(baseclassname4text.split("."))
ref = pyref(class_(baseclassname4text), module=baseclass.__module__, class_=baseclassname)
bases.append(ref)
bases = bases.withsep(", ")
bases.insert(0, "\u200b(") # use "ZERO WIDTH SPACE" to allow linebreaks
bases.append(")")
node = section(
h(
"class ",
class_(name),
bases,
":"
),
doc,
role=(visibility, " class ", hasdoc),
id=id
)
# find methods, properties and classes, but filter out those methods that are attribute getters, setters or deleters
all = []
properties = []
classes = []
for varname in thing.__dict__:
obj = getattr(thing, varname)
if isinstance(obj, property):
properties.append((obj, varname))
_append(all, obj, varname)
elif inspect.isclass(obj):
for (superclass, supername) in context:
if obj is superclass: # avoid endless recursion for classes that reference a class further up in the context path.
break
else:
classes.append((obj, varname))
_append(all, obj, varname)
elif inspect.isfunction(obj):
# skip the method if it's a property getter, setter or deleter
for (prop, name) in properties:
if obj==prop.fget or obj==prop.fset or obj==prop.fdel:
break
else:
_append(all, obj, varname)
if all:
all.sort()
for (key, subobj, subname) in all:
node.append(explain(subobj, subname, format, context))
return node
elif inspect.ismodule(thing):
name = name or thing.__name__
context = [(thing, name)]
node = xsc.Frag(doc)
all = []
for varname in thing.__dict__:
obj = getattr(thing, varname)
if inspect.isfunction(obj) or inspect.isclass(obj):
_append(all, obj, varname)
if all:
all.sort()
for (key, obj, name) in all:
node.append(
explain(obj, name, format, context),
)
return node
return xsc.Null
[docs]
class base(xsc.Element):
"""
The base of all element classes. Used for dispatching to conversion targets.
"""
xmlns = xmlns
register = False
class Context(xsc.Element.Context):
def __init__(self):
xsc.Element.Context.__init__(self)
self.stack = []
self.sections = [0]
self.firstheaderlevel = None
self.llblue = "#006499"
self.llgreen = "#9fc94d"
self.ttfont = "CourierNew, monospace"
self.hdfont = "ArialNarrow, Arial, sans-serif"
self.font = "PalatinoLinotype, serif"
self.indentcount = 0
self.vspaceattrs = dict(
space_before="0pt",
space_after_minimum="4pt",
space_after_optimum="6pt",
space_after_maximum="12pt",
space_after_conditionality="discard",
)
self.linkattrs = dict(
color="blue",
text_decoration="underline",
)
self.codeattrs = dict(
font_family=self.ttfont,
)
self.repattrs = dict(
font_style="italic",
)
self.emattrs = dict(
font_style="italic",
)
self.strongattrs = dict(
font_weight="bold",
)
def dedent(self):
return "-0.7cm"
def indent(self):
return f"{0.7*self.indentcount:.1f}cm"
def labelindent(self):
return f"{0.7*self.indentcount-0.4:.1f}cm"
def convert(self, converter):
target = converter.target
if target.xmlns == docbook.xmlns:
return self.convert_docbook(converter)
elif target.xmlns == html.xmlns:
return self.convert_html(converter)
elif target.xmlns == xmlns: # our own namespace
return self.convert_doc(converter)
elif target.xmlns == fo.xmlns:
return self.convert_fo(converter)
else:
raise ValueError(f"unknown conversion target {target!r}")
def convert_doc(self, converter):
e = self.__class__(
self.content.convert(converter),
self.attrs.convert(converter)
)
return e
[docs]
class block(base):
"""
Base class for all block level elements.
"""
xmlns = xmlns
register = False
def convert_html(self, converter):
e = converter.target.div(self.content)
return e.convert(converter)
[docs]
class inline(base):
"""
Base class for all inline elements.
"""
xmlns = xmlns
register = False
def convert_html(self, converter):
e = converter.target.span(self.content)
return e.convert(converter)
[docs]
class abbr(inline):
"""
Abbreviation.
"""
xmlns = xmlns
model = sims.NoElements()
class Attrs(xsc.Element.Attrs):
class title(xsc.TextAttr): pass
class lang(xsc.TextAttr): pass
def convert_docbook(self, converter):
e = converter.target.abbrev(self.content, lang=self.attrs.lang)
return e.convert(converter)
def convert_html(self, converter):
e = converter.target.abbr(self.content, self.attrs)
return e.convert(converter)
def convert_fo(self, converter):
return xsc.Text(str(self.content))
def __str__(self):
return str(self.content)
[docs]
class tab(xsc.Element):
"""
Used for displaying a tab character in the HTML output.
"""
xmlns = xmlns
register = False
def convert(self, converter):
e = converter.target.span("\xB7\xA0\xA0", class_="tab")
return e.convert(converter)
[docs]
class litblock(block):
"""
A literal text block (like source code or a shell session).
"""
xmlns = xmlns
model = sims.ElementsOrText(inline)
cssclass = "litblock"
def convert_html(self, converter):
target = converter.target
e = target.pre(class_=self.cssclass)
for child in self.content:
child = child.convert(converter)
if isinstance(child, xsc.Text):
for c in child.content:
if c=="\t":
c = tab()
e.append(c)
else:
e.append(child)
return e.convert(converter)
def convert_fo(self, converter):
target = converter.target
context = converter[self]
context.indentcount += 1
e = target.block(
context.vspaceattrs,
context.codeattrs,
text_align="left",
line_height="130%",
font_size="90%",
start_indent=context.indent(),
end_indent=context.indent()
)
collect = target.block()
first = True
for child in self.content:
child = child.convert(converter)
if isinstance(child, xsc.Text):
for c in child.content:
# We have to do the following, because FOP doesn't support the white-space property yet
if c==" ":
c = "\xa0" # transform spaces into nbsps
if c=="\t":
c = target.inline("\u25ab\xa0\xa0\xa0", color="rgb(50%, 50%, 50%)")
if c=="\n":
if not collect and not first: # fix empty lines (but not the first one)
collect.append("\ufeff")
collect["line_height"] = "60%" # reduce the line-height
e.append(collect)
collect = target.block()
first = False
else:
collect.append(c)
else:
collect.append(child)
if collect:
e.append(collect)
context.indentcount -= 1
return e.convert(converter)
[docs]
class prog(litblock):
"""
A literal listing of all or part of a program.
"""
xmlns = xmlns
cssclass = "prog"
def convert_docbook(self, converter):
e = converter.target.programlisting(self.content)
return e.convert(converter)
[docs]
class tty(litblock):
"""
A dump of a shell session.
"""
xmlns = xmlns
cssclass = "tty"
def convert_docbook(self, converter):
e = converter.target.screen(self.content)
return e.convert(converter)
[docs]
class prompt(inline):
"""
The prompt in a :class:`tty` dump.
"""
xmlns = xmlns
def convert_docbook(self, converter):
e = converter.target.prompt(self.content)
return e.convert(converter)
def convert_html(self, converter):
e = converter.target.code(self.content, class_="prompt")
return e.convert(converter)
def convert_fo(self, converter):
return xsc.Text(str(self.content))
[docs]
class rep(inline):
"""
Content that may or must be replaced by the user.
"""
xmlns = xmlns
model = sims.NoElements()
def convert_docbook(self, converter):
e = converter.target.replaceable(self.content)
return e.convert(converter)
def convert_html(self, converter):
e = converter.target.var(self.content, class_="rep")
return e.convert(converter)
def convert_fo(self, converter):
e = converter.target.inline(self.content, converter[self].repattrs)
return e.convert(converter)
class code(inline):
xmlns = xmlns
register = False
def convert_fo(self, converter):
e = converter.target.inline(
self.content,
converter[self].codeattrs
)
return e.convert(converter)
def convert_html(self, converter):
e = converter.target.code(self.content, class_=self.xmlname)
return e.convert(converter)
[docs]
class option(code):
"""
An option for a software command.
"""
xmlns = xmlns
model = sims.ElementsOrText(rep)
def convert_docbook(self, converter):
e = converter.target.option(self.content)
return e.convert(converter)
def convert_html(self, converter):
e = converter.target.code(self.content, class_="option")
return e.convert(converter)
[docs]
class lit(code):
"""
Inline text that is some literal value.
"""
xmlns = xmlns
model = sims.ElementsOrText(code, rep)
def convert_docbook(self, converter):
e = converter.target.literal(self.content)
return e.convert(converter)
def convert_html(self, converter):
e = converter.target.code(self.content, class_="lit")
return e.convert(converter)
[docs]
class func(code):
"""
The name of a function or subroutine, as in a programming language.
"""
xmlns = xmlns
model = sims.ElementsOrText(rep)
def convert_docbook(self, converter):
e = converter.target.function(self.content)
return e.convert(converter)
[docs]
class meth(code):
"""
The name of a method or memberfunction in a programming language.
"""
xmlns = xmlns
model = sims.ElementsOrText(rep)
def convert_docbook(self, converter):
e = converter.target.methodname(self.content)
return e.convert(converter)
[docs]
class attr(code):
"""
The name of an attribute of a class/object.
"""
xmlns = xmlns
model = sims.ElementsOrText(rep)
def convert_docbook(self, converter):
e = converter.target.methodname(self.content)
return e.convert(converter)
[docs]
class prop(code):
"""
The name of a property in a programming language.
"""
xmlns = xmlns
model = sims.ElementsOrText(rep)
def convert_docbook(self, converter):
e = converter.target.varname(self.content, role="property")
return e.convert(converter)
[docs]
class class_(code):
"""
The name of a class, in the object-oriented programming sense.
"""
xmlns = xmlns
xmlname = "class"
model = sims.ElementsOrText(rep)
def convert_docbook(self, converter):
e = converter.target.classname(self.content)
return e.convert(converter)
[docs]
class exc(code):
"""
The name of an exception class.
"""
xmlns = xmlns
model = sims.ElementsOrText(rep)
def convert_docbook(self, converter):
e = converter.target.classname(self.content)
return e.convert(converter)
[docs]
class markup(code):
"""
A string of formatting markup in text that is to be represented literally.
"""
xmlns = xmlns
model = sims.ElementsOrText(rep)
def convert_docbook(self, converter):
e = converter.target.markup(self.content)
return e.convert(converter)
def convert_html(self, converter):
e = converter.target.code(self.content, class_="markup")
return e.convert(converter)
[docs]
class self(code):
"""
Use this class when referring to the object for which a method has been
called, e.g.::
this function fooifies the object <self/>;.
"""
xmlns = xmlns
model = sims.Empty()
def convert_docbook(self, converter):
e = converter.target.varname("self")
return e.convert(converter)
def convert_html(self, converter):
e = converter.target.code("self", class_="self")
return e.convert(converter)
def convert_fo(self, converter):
e = converter.target.inline("self", converter[self].codeattrs)
return e.convert(converter)
self_ = self
[docs]
class cls(inline):
"""
Use this class when referring to the object for which a class method has
been called, e.g.::
this function fooifies the class <cls/>.
"""
xmlns = xmlns
model = sims.Empty()
def convert_docbook(self, converter):
e = converter.target.varname("cls")
return e.convert(converter)
def convert_html(self, converter):
e = converter.target.code("cls", class_="cls")
return e.convert(converter)
def convert_fo(self, converter):
e = converter.target.inline("cls", converter[self].codeattrs)
return e.convert(converter)
[docs]
class obj(code):
"""
A object of unspecified type.
"""
xmlns = xmlns
model = sims.ElementsOrText(rep, self, cls)
def convert_docbook(self, converter):
e = converter.target.varname(self.content)
return e.convert(converter)
[docs]
class mod(code):
"""
The name of a Python module.
"""
xmlns = xmlns
model = sims.ElementsOrText(rep)
def convert_docbook(self, converter):
e = converter.target.classname(self.content, role="module")
return e.convert(converter)
def convert_html(self, converter):
e = converter.target.code(self.content, class_="module")
return e.convert(converter)
[docs]
class file(code):
"""
The name of a file.
"""
xmlns = xmlns
model = sims.ElementsOrText(rep)
def convert_docbook(self, converter):
e = converter.target.filename(self.content)
return e.convert(converter)
def convert_html(self, converter):
e = converter.target.code(self.content, class_="filename")
return e.convert(converter)
[docs]
class dir(code):
"""
The name of a directory.
"""
xmlns = xmlns
model = sims.ElementsOrText(rep)
def convert_docbook(self, converter):
e = converter.target.filename(self.content, class_="directory")
return e.convert(converter)
[docs]
class user(code):
"""
The name of a user account.
"""
xmlns = xmlns
model = sims.ElementsOrText(rep)
def convert_docbook(self, converter):
e = converter.target.literal(self.content, role="username")
return e.convert(converter)
[docs]
class host(code):
"""
The name of a computer.
"""
xmlns = xmlns
model = sims.ElementsOrText(rep)
def convert_docbook(self, converter):
e = converter.target.literal(self.content, role="hostname")
return e.convert(converter)
[docs]
class const(code):
"""
The name of a constant.
"""
xmlns = xmlns
model = sims.ElementsOrText(rep)
def convert_docbook(self, converter):
e = converter.target.literal(self.content, role="constant")
return e.convert(converter)
[docs]
class data(code):
"""
The name of a data object.
"""
xmlns = xmlns
model = sims.ElementsOrText(rep)
def convert_docbook(self, converter):
e = converter.target.literal(self.content, role="data")
return e.convert(converter)
[docs]
class app(inline):
"""
The name of a software program.
"""
xmlns = xmlns
model = sims.ElementsOrText(rep)
class Attrs(xsc.Element.Attrs):
class moreinfo(xsc.URLAttr): pass
def convert_docbook(self, converter):
e = converter.target.application(self.content, moreinfo=self.attrs.moreinfo)
return e.convert(converter)
def convert_html(self, converter):
if "moreinfo" in self.attrs:
e = converter.target.a(self.content, class_="app", href=self.attrs.moreinfo)
else:
e = converter.target.span(self.content, class_="app")
return e.convert(converter)
def convert_fo(self, converter):
if "moreinfo" in self.attrs:
e = converter.target.basic_link(
self.content,
converter[self].linkattrs,
external_destination=self.attrs.moreinfo
)
else:
e = self.content
return e.convert(converter)
[docs]
class h(base):
"""
The text of the title of a :class:`section` or an :class:`example`.
"""
xmlns = xmlns
model = sims.ElementsOrText(inline)
def convert_docbook(self, converter):
e = converter.target.title(self.content.convert(converter))
return e.convert(converter)
def convert_html(self, converter):
context = converter[self]
if context.stack:
if isinstance(context.stack[-1], example):
e = self.content
elif isinstance(context.stack[-1], section):
level = len(context.sections)
if context.firstheaderlevel is None:
context.firstheaderlevel = level
e = getattr(converter.target, f"h{context.firstheaderlevel+level}", converter.target.h6)(self.content)
else:
raise ValueError(f"unknown node {context.stack[-1]!r} on the stack")
else:
context.firstheaderlevel = 0
e = converter.target.h1(self.content)
return e.convert(converter)
def convert_fo(self, converter):
e = self.content
return e.convert(converter)
[docs]
class section(block):
"""
A recursive section.
"""
xmlns = xmlns
model = sims.Elements(h, block)
class Attrs(xsc.Element.Attrs):
class role(xsc.TextAttr): pass
class id(xsc.IDAttr): pass
def convert_docbook(self, converter):
e = converter.target.section(self.content, role=self.attrs.role, id=self.attrs.id)
return e.convert(converter)
def convert_html(self, converter):
target = converter.target
context = converter[self]
context.sections[-1] += 1
level = len(context.sections)
context.sections.append(0) # for numbering the subsections
ts = xsc.Frag()
cs = html.div(class_="content")
for child in self:
if isinstance(child, h):
ts.append(child)
else:
cs.append(child)
e = target.div(class_=("section level", level), id=self.attrs.id)
if "role" in self.attrs:
e.attrs.class_.append(" ", self.attrs.role)
#if "id" in self.attrs:
# e.append(target.a(name=self.attrs.id, id=self.attrs.id))
hclass = getattr(target, f"h{level}", target.h6)
for t in ts:
e.append(hclass(t.content))
e.append(cs)
with _stack(context, self):
# make sure to call the inner convert() before popping the number off of the stack
e = e.convert(converter)
del context.sections[-1]
return e
def convert_fo(self, converter):
context = converter[self]
context.sections[-1] += 1
context.sections.append(0)
ts = xsc.Frag()
cs = xsc.Frag()
props = [
# size, before, after
("30pt", "30pt", "2pt"),
("22pt", "20pt", "2pt"),
("16pt", "15pt", "2pt"),
("12pt", "15pt", "2pt")
]
for child in self.content:
if isinstance(child, h):
ts.append(child.content)
else:
cs.append(child)
p = props[min(len(context.sections)-1, len(props)-1)]
isref = str(self.attrs.role.convert(converter)) in ("class", "method", "property", "function", "module")
number = None
if isref:
context.indentcount += 1
text_indent = context.dedent()
else:
if len(context.sections)>1:
number = (
".".join(str(s) for s in context.sections[:-1]),
". "
)
text_indent = None
tattrs = fo.block.Attrs(
font_size=p[0],
color=context.llblue,
space_before=p[1],
space_after=p[2],
text_align="left",
font_family=context.hdfont,
keep_with_next_within_page="always",
text_indent=text_indent
)
e = fo.block(
fo.block(number, ts, tattrs),
cs,
start_indent=context.indent()
)
e = e.convert(converter)
del context.sections[-1]
if isref:
context.indentcount -= 1
return e
[docs]
class p(block):
"""
A paragraph.
"""
xmlns = xmlns
model = sims.ElementsOrText(inline)
class Attrs(xsc.Element.Attrs):
class type(xsc.TextAttr): pass
def convert_docbook(self, converter):
e = converter.target.para(self.content, role=self.attrs.type)
return e.convert(converter)
def convert_html(self, converter):
e = converter.target.p(self.content, class_=self.attrs.type)
return e.convert(converter)
def convert_fo(self, converter):
e = fo.block(
self.content,
converter[self].vspaceattrs,
line_height="130%"
)
return e.convert(converter)
[docs]
class dt(block):
"""
A term inside a :class:`dl`.
"""
xmlns = xmlns
model = sims.ElementsOrText(inline)
def convert_docbook(self, converter):
e = converter.target.term(self.content)
return e.convert(converter)
def convert_html(self, converter):
e = converter.target.dt(self.content)
return e.convert(converter)
def convert_fo(self, converter):
e = converter.target.block(
self.content,
font_style="italic"
)
return e.convert(converter)
[docs]
class li(block):
"""
A wrapper for the elements of a list item in :class:`ul` or :class:`ol`.
"""
xmlns = xmlns
model = sims.ElementsOrText(block, inline) # if it contains no block elements, the content will be promoted to a paragraph
def convert_docbook(self, converter):
if self[block]:
content = self.content
else:
content = converter.target.para(self.content)
e = converter.target.listitem(content)
return e.convert(converter)
def convert_html(self, converter):
e = converter.target.li(self.content)
return e.convert(converter)
def convert_fo(self, converter):
target = converter.target
context = converter[self]
context.lists[-1][1] += 1
type = context.lists[-1][0]
if type=="ul":
label = "\u2022"
elif type=="ol":
label = f"{context.lists[-1][1]}."
context.indentcount += 1
if self[block]: # Do we have a block in our content?
content = self.content # yes => use the content as is
else:
content = p(self.content) # no => wrap it in a paragraph
e = target.list_item(
target.list_item_label(
target.block(label),
start_indent=context.labelindent()
),
target.list_item_body(
content,
start_indent=context.indent()
)
)
context.indentcount -= 1
return e.convert(converter)
[docs]
class dd(block):
"""
A wrapper for the elements of a list item :class:`dl`.
"""
xmlns = xmlns
model = sims.ElementsOrText(block, inline) # if it contains no block elements, the content will be promoted to a paragraph
def convert_docbook(self, converter):
if self[block]:
content = self.content
else:
content = converter.target.para(self.content)
e = converter.target.listitem(content)
return e.convert(converter)
def convert_html(self, converter):
e = converter.target.dd(self.content)
return e.convert(converter)
def convert_fo(self, converter):
target = converter.target
context = converter[self]
context.lists[-1][1] += 1
type = context.lists[-1][0]
context.indentcount += 1
if self[block]: # Do we have a block in our content?
content = self.content # yes => use the content as is
else:
content = p(self.content) # no => wrap it in a paragraph
e = target.block(
content,
start_indent=context.indent()
)
context.indentcount -= 1
return e.convert(converter)
[docs]
class list(block):
"""
Common baseclass for :class:`ul`, :class:`ol` and :class:`dl`.
"""
xmlns = xmlns
register = False
[docs]
class ul(list):
"""
A list in which each entry is marked with a bullet or other dingbat.
"""
xmlns = xmlns
model = sims.Elements(li)
def convert_docbook(self, converter):
e = converter.target.itemizedlist(self.content.convert(converter))
return e.convert(converter)
def convert_html(self, converter):
with _stack(converter[self], self):
return converter.target.ul(self.content.convert(converter))
def convert_fo(self, converter):
context = converter[self]
context.lists.append(["ul", 0])
e = converter.target.list_block(self.content, line_height="130%")
e = e.convert(converter)
del context.lists[-1]
return e
[docs]
class ol(list):
"""
A list in which each entry is marked with a sequentially incremented label.
"""
xmlns = xmlns
model = sims.Elements(li)
def convert_docbook(self, converter):
e = converter.target.orderedlist(self.content.convert(converter))
return e.convert(converter)
def convert_html(self, converter):
with _stack(converter[self], self):
return converter.target.ol(self.content.convert(converter))
def convert_fo(self, converter):
context = converter[self]
context.lists.append(["ol", 0])
e = converter.target.list_block(self.content, line_height="130%")
e = e.convert(converter)
del context.lists[-1]
return e
[docs]
class dl(list):
"""
A list in which each entry is marked with a label.
"""
xmlns = xmlns
model = sims.Elements(dt, dd)
def convert_docbook(self, converter):
e = converter.target.variablelist()
collect = converter.target.varlistentry()
for child in self.content:
collect.append(child)
if isinstance(child, dd):
e.append(collect)
collect = converter.target.varlistentry()
if collect:
e.append(collect)
return e.convert(converter)
def convert_html(self, converter):
with _stack(converter[self], self):
return converter.target.dl(self.content.convert(converter))
def convert_fo(self, converter):
context = converter[self]
context.lists.append(["dl", 0])
e = self.content.convert(converter)
del context.lists[-1]
return e
[docs]
class example(block):
"""
A formal example.
"""
xmlns = xmlns
model = sims.Elements(h, block)
def convert_docbook(self, converter):
e = converter.target.example(self.content)
return e.convert(converter)
def convert_html(self, converter):
target = converter.target
ts = xsc.Frag()
e = xsc.Frag()
for child in self:
if isinstance(child, h):
ts.append(child)
else:
e.append(child)
if ts:
e.append(target.div(ts, class_="example-title"))
with _stack(converter[self], self):
return e.convert(converter)
def convert_fo(self, converter):
# FIXME: handle title
e = xsc.Frag()
for child in self.content:
if not isinstance(child, h):
e.append(child)
return e.convert(converter)
[docs]
class a(inline):
"""
A hypertext link.
"""
xmlns = xmlns
model = sims.ElementsOrText(inline)
class Attrs(xsc.Element.Attrs):
class href(xsc.URLAttr): pass
class hreflang(xsc.TextAttr): pass
def convert_docbook(self, converter):
e = converter.target.link(self.content, linkend=self.attrs.href)
return e.convert(converter)
def convert_html(self, converter):
e = converter.target.a(self.content, href=self.attrs.href, hreflang=self.attrs.hreflang)
return e.convert(converter)
def convert_fo(self, converter):
if "href" in self.attrs:
e = converter.target.basic_link(
self.content,
converter[self].linkattrs,
external_destination=self.attrs.href
)
else:
e = self.content
return e.convert(converter)
[docs]
class xref(inline):
"""
An internal cross reference.
"""
xmlns = xmlns
model = sims.ElementsOrText(inline)
class Attrs(xsc.Element.Attrs):
class ref(xsc.TextAttr): pass
def convert_docbook(self, converter):
e = converter.target.link(self.content, linkend=self.attrs.ref)
return e.convert(converter)
def convert_html(self, converter):
e = converter.target.a(self.content, href=("#", self.attrs.ref))
return e.convert(convertert)
def convert_fo(self, converter):
if "href" in self.attrs:
e = converter.target.basic_link(
self.content,
converter[self].linkattrs,
internal_destination=self.attrs.ref
)
else:
e = self.content
return e.convert(converter)
[docs]
class email(inline):
"""
An email address.
"""
xmlns = xmlns
model = sims.NoElements()
def convert_docbook(self, converter):
e = converter.target.email(self.content)
return e.convert(converter)
def convert_html(self, converter):
e = converter.target.a(self.content, href=("mailto:", self.content))
return e.convert(converter)
def convert_fo(self, converter):
e = converter.target.basic_link(
self.content,
converter[self].linkattrs,
external_destination=("mailto:", self.content)
)
return e.convert(converter)
[docs]
class em(inline):
"""
Emphasized text.
"""
xmlns = xmlns
model = sims.ElementsOrText(inline)
def convert_docbook(self, converter):
e = converter.target.emphasis(self.content)
return e.convert(converter)
def convert_html(self, converter):
e = converter.target.em(self.content)
return e.convert(converter)
def convert_fo(self, converter):
e = converter.target.inline(
self.content,
converter[self].emattrs
)
return e.convert(converter)
[docs]
class strong(inline):
"""
Emphasized text.
"""
xmlns = xmlns
model = sims.ElementsOrText(inline)
def convert_docbook(self, converter):
e = converter.target.emphasis(self.content)
return e.convert(converter)
def convert_html(self, converter):
e = converter.target.strong(self.content)
return e.convert(converter)
def convert_fo(self, converter):
e = converter.target.inline(
self.content,
converter[self].strongattrs
)
return e.convert(converter)
[docs]
class z(inline):
"""
Put the content into double quotes.
"""
xmlns = xmlns
model = sims.ElementsOrText(inline)
def convert(self, converter):
e = xsc.Frag("\u201c", self.content, "\u201d")
return e.convert(converter)
[docs]
class pyref(inline):
"""
Reference to a Python object: module, class, method, property or function.
"""
xmlns = xmlns
model = sims.ElementsOrText(inline)
class Attrs(xsc.Element.Attrs):
class module(xsc.TextAttr): pass
class class_(xsc.TextAttr): xmlname = "class"
class method(xsc.TextAttr): pass
class property(xsc.TextAttr): pass
class function(xsc.TextAttr): pass
class Context(xsc.Element.Context):
def __init__(self):
xsc.Element.Context.__init__(self)
self.base = "http://127.0.0.1:7464/"
def convert(self, converter):
target = converter.target
context = converter[self]
if target.xmlns == xmlns: # our own namespace
return self.convert_doc(converter)
if "function" in self.attrs:
function = str(self.attrs.function.convert(converter))
else:
function = None
if "method" in self.attrs:
method = str(self.attrs.method.convert(converter))
else:
method = None
if "property" in self.attrs:
prop = str(self.attrs.property.convert(converter))
else:
prop = None
if "class" in self.attrs:
class_ = str(self.attrs.class_.convert(converter)).replace(".", "-")
else:
class_ = None
if "module" in self.attrs:
module = str(self.attrs.module.convert(converter))
if module.startswith("ll."):
module = module[3:].replace(".", "/")
elif module == "ll":
module = "core"
else:
module = None
else:
module = None
e = self.content
if target.xmlns == html.xmlns:
if function is not None:
if module is not None:
e = target.a(e, href=(context.base, module, "/index.html#", function))
elif method is not None:
if class_ is not None and module is not None:
e = target.a(e, href=(context.base, module, "/index.html#", class_, "-", method))
elif prop is not None:
if class_ is not None and module is not None:
e = target.a(e, href=(context.base, module, "/index.html#", class_, "-", prop))
elif class_ is not None:
if module is not None:
e = target.a(e, href=(context.base, module, "/index.html#", class_))
elif module is not None:
e = target.a(e, href=(context.base, module, "/index.html"))
return e.convert(converter)
class fodoc(base):
xmlns = xmlns
model = sims.Elements(block)
def convert(self, converter):
context = converter[self]
e = self.content
converter.push(target=sys.modules[__name__]) # our own module
e = e.convert(converter)
converter.pop()
converter.push(target=fo)
e = e.convert(converter)
converter.pop()
e = xsc.Frag(
xml.XML(), "\n",
fo.root(
fo.layout_master_set(
fo.simple_page_master(
fo.region_body(
region_name="xsl-region-body",
margin_bottom="3cm"
),
fo.region_after(
region_name="xsl-region-after",
extent="2cm"
),
master_name="default",
page_height="29.7cm",
page_width="21cm",
margin_top="1cm",
margin_bottom="1cm",
margin_left="2.5cm",
margin_right="1cm"
)
),
fo.page_sequence(
fo.static_content(
fo.block(
fo.page_number(),
border_before_width="0.1pt",
border_before_color="#000",
border_before_style="solid",
padding_before="4pt",
text_align="center"
),
flow_name="xsl-region-after"
),
fo.flow(
e,
flow_name="xsl-region-body"
),
master_reference="default"
),
font_family=context.font,
font_size="10pt",
text_align="justify",
line_height="normal",
language="en",
orphans=2,
widows=3
)
)
return e