""" a panflute filter to format Span element
representations of RawInline elements
The :py:mod:`ipypublish.filters_pandoc.prepare_raw` filter should be run
first to access the functionality below:
"""
import itertools
# from textwrap import fill as textwrap
from panflute import Element, Doc, Span # noqa: F401
import panflute as pf
from ipypublish.filters_pandoc.definitions import (
CONVERTED_OTHER_CLASS,
CONVERTED_DIRECTIVE_CLASS,
IPUB_META_ROUTE,
)
[docs]def process_raw_spans(container, doc):
# type: (Span, Doc) -> Element
if not isinstance(container, (pf.Span, pf.Div)):
return None
hide_raw = doc.get_metadata(IPUB_META_ROUTE + ".hide_raw", False)
if CONVERTED_OTHER_CLASS in container.classes and isinstance(container, pf.Span):
if doc.format == "rst" and container.attributes["format"] == "latex":
if container.attributes["tag"] in ["todo"]:
return pf.Str(
"\n\n.. {}:: {}\n\n".format(
container.attributes["tag"], container.attributes["content"]
)
)
if container.attributes["tag"] == "ensuremath":
return pf.RawInline(
":math:`{}`".format(container.attributes["content"]), format="rst"
)
return pf.RawInline(
container.attributes.get("original"), format=container.attributes["format"]
)
if CONVERTED_DIRECTIVE_CLASS in container.classes and isinstance(container, pf.Div):
# convert the directive head, which will be e.g.
# Para(Str(..) Space Str(toctree::) SoftBreak Str(:maxdepth:) Space Str(2) SoftBreak Str(:numbered:)) # noqa
# we need to spilt on the soft breaks,
# place them on a new line and re-indent them
if doc.format in ("rst"):
# split into lines by soft breaks
header_lines = [
list(y)
for x, y in itertools.groupby(
container.content[0].content, lambda z: isinstance(z, pf.SoftBreak)
)
if not x
]
# wrap each line in a Para and convert block with pandoc
head_doc = pf.Doc(*[pf.Para(*l) for l in header_lines])
head_doc.api_version = doc.api_version
head_str = pf.convert_text(
head_doc, input_format="panflute", output_format=doc.format
)
# remove blank lines and indent
head_str = head_str.replace("\n\n", "\n ") + "\n\n"
head_block = pf.RawBlock(head_str, format=doc.format)
if len(container.content) == 1:
return head_block
# split into lines by soft breaks, we use indicators to tell
# us where to indent in the converted text
body_blocks = []
for block in container.content[1:]:
new_elements = [pf.RawInline("%^*", format=doc.format)]
for el in block.content:
if isinstance(el, pf.SoftBreak):
new_elements.append(pf.RawInline("?&@", format=doc.format))
else:
new_elements.append(el)
block.content = new_elements
body_blocks.append(block)
# convert body content with pandoc
body_doc = pf.Doc(*body_blocks)
body_doc.api_version = doc.api_version
body_str = pf.convert_text(
body_doc, input_format="panflute", output_format=doc.format
)
# raise ValueError(body_blocks)
body_str = body_str.replace("%^*", " ").replace("?&@", "\n ")
# ensure all lines are indented correctly
# (doesn't occur by default?)
body_str = (
"\n".join(
[
" " + l.lstrip() if l.strip() else l
for l in body_str.splitlines()
]
)
+ "\n\n"
)
body_block = pf.RawBlock(body_str, format=doc.format)
return [head_block, body_block]
elif (
doc.format in ("html", "html5") and container.attributes["format"] == "rst"
):
if hide_raw:
return []
head_para = pf.Para(
*[
pf.RawInline("<br>" + " " * 4)
if isinstance(c, pf.SoftBreak)
else c
for c in container.content[0].content
]
)
head_str = pf.convert_text(
head_para, input_format="panflute", output_format=doc.format
)
if len(container.content) > 1:
body_doc = pf.Doc(*container.content[1:])
body_doc.api_version = doc.api_version
body_str = pf.convert_text(
body_doc, input_format="panflute", output_format=doc.format
)
body_str = (
'<p></p><div style="margin-left: 20px">' "{0}</div>"
).format(body_str)
else:
body_str = ""
return pf.RawBlock(
'<div {0} style="background-color:rgba(10, 225, 10, .2)">'
"{1}{2}"
"</div>".format(
container.attributes.get("directive", ""), head_str, body_str
),
format="html",
)
elif doc.format in ("tex", "latex") and container.attributes["format"] == "rst":
if hide_raw:
return []
directive = container.attributes.get("directive", "")
inline = container.attributes.get("inline", "")
# TODO handle directive with options and/or inline body
# e.g. .. figure:: path/to/figure
# :centre:
box_open = (
"\\begin{{mdframed}}"
"[frametitle={{{0}}},frametitlerule=true]".format(directive)
)
if inline:
box_open += "\n\\mdfsubtitle{{{0}}}".format(inline)
box_close = "\\end{mdframed}"
if len(container.content) == 1:
return pf.RawBlock(box_open + box_close, format="tex")
else:
return (
[pf.RawBlock(box_open, format="tex")]
+ list(container.content[1:])
+ [pf.RawBlock(box_close, format="tex")]
)
return pf.RawBlock(
pf.stringify(pf.Doc(*container.content)),
format=container.attributes["format"],
)
if CONVERTED_OTHER_CLASS in container.classes and isinstance(container, pf.Div):
return pf.RawBlock(
pf.stringify(pf.Doc(*container.content)),
format=container.attributes["format"],
)
# now unused
# def split_soft_breaks(container,
# indent=4, fmt="rst", indent_first=False,
# pre_content="", post_content="",
# pre_chunk="", post_chunk="",
# linebreak="\n", raw_indent=None):
# """rst conversion doesn't recognise soft breaks as new lines,
# so add them manually and return a list containing the new elements
# """
# content = []
# if pre_content:
# content.append(pf.RawBlock(pre_content, fmt))
# chunks = [list(y) for x, y in itertools.groupby(
# container.content,
# lambda z: isinstance(z, pf.SoftBreak)) if not x]
# for i, chunk in enumerate(chunks):
# if i > 0 or indent_first:
# if raw_indent is not None:
# chunk = [pf.RawInline(raw_indent, fmt)] * indent + chunk
# else:
# chunk = [pf.Space()] * indent + chunk
# if pre_chunk:
# content.append(pf.RawBlock(pre_chunk, fmt))
# content.append(pf.Plain(*chunk))
# content.append(pf.RawBlock(linebreak, fmt))
# if post_chunk:
# content.append(pf.RawBlock(post_chunk, fmt))
# # if isinstance(container, pf.Para):
# # content.append(pf.RawBlock(linebreak, fmt))
# if post_content:
# content.append(pf.RawBlock(post_content, fmt))
# return content
[docs]def process_code_latex(code, doc):
# type: (pf.CodeBlock, Doc) -> Element
if doc.format not in ("tex", "latex"):
return None
if not isinstance(code, pf.CodeBlock):
return None
# TODO line wrapping
return [
pf.RawBlock("\\begin{mdframed}", format=doc.format),
code,
pf.RawBlock("\\end{mdframed}", format=doc.format),
]
[docs]def prepare(doc):
# type: (Doc) -> None
pass
[docs]def finalize(doc):
# type: (Doc) -> None
pass
[docs]def main(doc=None):
# type: (Doc) -> None
"""
"""
return pf.run_filters(
[process_raw_spans, process_code_latex], prepare, finalize, doc=doc
)
if __name__ == "__main__":
main()