Compare commits
No commits in common. "master" and "b9c193d45f6706c34aa228eff7e26f67959329b0" have entirely different histories.
master
...
b9c193d45f
26 changed files with 297 additions and 346 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -1,6 +1,3 @@
|
||||||
[submodule "ucwmac"]
|
[submodule "ucwmac"]
|
||||||
path = ucwmac
|
path = ucwmac
|
||||||
url = git://git.ucw.cz/ucwmac.git
|
url = git://git.ucw.cz/ucwmac.git
|
||||||
[submodule "src/formatitko/katex-server"]
|
|
||||||
path = src/formatitko/katex-server
|
|
||||||
url = https://gitea.ks.matfyz.cz:/KSP/formatitko-katex-server
|
|
||||||
|
|
|
@ -59,7 +59,7 @@ definition lists and citations. It also adds its own custom features.
|
||||||
|
|
||||||
Flags can be set in the Front Matter or with python code. Then, elements with
|
Flags can be set in the Front Matter or with python code. Then, elements with
|
||||||
the `if` attribute will only be shown if the flag is set to True and elements
|
the `if` attribute will only be shown if the flag is set to True and elements
|
||||||
with the `ifnot` attribute will only be show if the flag is not set to True.
|
with the `ifn` attribute will only be show if the flag is not set to True.
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ flags:
|
||||||
|
|
||||||
[This will not be shown]{if=bar}
|
[This will not be shown]{if=bar}
|
||||||
|
|
||||||
[This will be shown]{ifnot=bar}
|
[This will be shown]{ifn=bar}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Including other files
|
### Including other files
|
||||||
|
|
|
@ -16,7 +16,7 @@ class InlineCommand(Span, Command):
|
||||||
if len(content) == 1 and (isinstance(content[0], Para) or isinstance(content[0], Plain)):
|
if len(content) == 1 and (isinstance(content[0], Para) or isinstance(content[0], Plain)):
|
||||||
return Span(*content[0].content)
|
return Span(*content[0].content)
|
||||||
else:
|
else:
|
||||||
return Div(*content)
|
raise InlineError(f"The command {self.attributes['c']} returned multiple Paragraphs and must be executed using `::: {{c={self.attributes['c']}}}\\n:::`.\n\n{content}")
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class BlockCommand(Div, Command):
|
class BlockCommand(Div, Command):
|
||||||
|
|
|
@ -5,5 +5,4 @@ from formatitko.util import parse_string
|
||||||
|
|
||||||
from formatitko.context import Context
|
from formatitko.context import Context
|
||||||
from formatitko.command import Command
|
from formatitko.command import Command
|
||||||
from .nop_processor import NOPProcessor
|
|
||||||
from panflute import Element
|
from panflute import Element
|
||||||
|
|
|
@ -15,7 +15,7 @@ def parse_command(code: str) -> CommandCallable:
|
||||||
indented_code_lines = []
|
indented_code_lines = []
|
||||||
for line in code_lines:
|
for line in code_lines:
|
||||||
indented_code_lines.append(("\t" if tabs else " ")+line)
|
indented_code_lines.append(("\t" if tabs else " ")+line)
|
||||||
code = "def command(element: Command, context: Context, processor: NOPProcessor) -> list[Element]:\n"+"\n".join(indented_code_lines)
|
code = "def command(element: Command, context: Context) -> list[Element]:\n"+"\n".join(indented_code_lines)
|
||||||
env = {**command_env.__dict__}
|
env = {**command_env.__dict__}
|
||||||
exec(code, env)
|
exec(code, env)
|
||||||
return env["command"]
|
return env["command"]
|
||||||
|
|
|
@ -3,10 +3,11 @@ from panflute import Doc, Element, Div, Span
|
||||||
from typing import Union, Callable
|
from typing import Union, Callable
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
import os
|
import os
|
||||||
|
import warnings
|
||||||
|
|
||||||
from .command import Command
|
from .command import Command
|
||||||
|
|
||||||
CommandCallable = Callable[[Command, 'Context', 'NOPProcessor'], list[Element]] # This is here because of a wild circular import dependency between many functions and classes
|
CommandCallable = Callable[[Command, 'Context'], list[Element]] # This is here because of a wild circular import dependency between many functions and classes
|
||||||
|
|
||||||
# This class is used to keep state while transforming the document using
|
# This class is used to keep state while transforming the document using
|
||||||
# transform.py. For the context to be available to the html and TeX generators,
|
# transform.py. For the context to be available to the html and TeX generators,
|
||||||
|
@ -22,7 +23,6 @@ CommandCallable = Callable[[Command, 'Context', 'NOPProcessor'], list[Element]]
|
||||||
class Context:
|
class Context:
|
||||||
parent: Union["Context", None]
|
parent: Union["Context", None]
|
||||||
_commands: dict[str, Union[CommandCallable, None]]
|
_commands: dict[str, Union[CommandCallable, None]]
|
||||||
_data: dict[str, object]
|
|
||||||
doc: Doc
|
doc: Doc
|
||||||
trusted: bool
|
trusted: bool
|
||||||
path: str
|
path: str
|
||||||
|
@ -30,12 +30,10 @@ class Context:
|
||||||
filename: str
|
filename: str
|
||||||
root_dir: str # Absolute path to the dir of the file formátítko was called on
|
root_dir: str # Absolute path to the dir of the file formátítko was called on
|
||||||
rel_dir: str # Relative path to the current dir from the root dir
|
rel_dir: str # Relative path to the current dir from the root dir
|
||||||
deps: set[str]
|
|
||||||
|
|
||||||
def __init__(self, doc: Doc, path: str, parent: Union['Context', None]=None, trusted: bool=True):
|
def __init__(self, doc: Doc, path: str, parent: Union['Context', None]=None, trusted: bool=True):
|
||||||
self.parent = parent
|
self.parent = parent
|
||||||
self._commands = {}
|
self._commands = {}
|
||||||
self._data = {}
|
|
||||||
self.doc = doc
|
self.doc = doc
|
||||||
self.trusted = trusted
|
self.trusted = trusted
|
||||||
self.path = path
|
self.path = path
|
||||||
|
@ -43,8 +41,6 @@ class Context:
|
||||||
self.filename = os.path.basename(path)
|
self.filename = os.path.basename(path)
|
||||||
self.root_dir = parent.root_dir if parent else os.path.abspath(self.dir)
|
self.root_dir = parent.root_dir if parent else os.path.abspath(self.dir)
|
||||||
self.rel_dir = os.path.relpath(self.dir, self.root_dir)
|
self.rel_dir = os.path.relpath(self.dir, self.root_dir)
|
||||||
self.deps = set()
|
|
||||||
self.add_dep(path)
|
|
||||||
if self.get_metadata("flags", immediate=True) is None:
|
if self.get_metadata("flags", immediate=True) is None:
|
||||||
self.set_metadata("flags", {})
|
self.set_metadata("flags", {})
|
||||||
|
|
||||||
|
@ -117,57 +113,7 @@ class Context:
|
||||||
else:
|
else:
|
||||||
self.set_metadata(key, data)
|
self.set_metadata(key, data)
|
||||||
|
|
||||||
def get_data(self, key: str, immediate: bool=False):
|
|
||||||
data = self._data
|
|
||||||
keys = key.split(".")
|
|
||||||
try:
|
|
||||||
for k in keys:
|
|
||||||
data = data[k]
|
|
||||||
return data
|
|
||||||
except KeyError:
|
|
||||||
if self.parent and not immediate:
|
|
||||||
return self.parent.get_data(key)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def set_data(self, key: str, value: object):
|
|
||||||
data = self._data
|
|
||||||
keys = key.split(".")
|
|
||||||
for k in keys[:-1]:
|
|
||||||
try:
|
|
||||||
data = data[k]
|
|
||||||
except KeyError:
|
|
||||||
data[k] = {}
|
|
||||||
data = data[k]
|
|
||||||
data[keys[-1]] = value
|
|
||||||
|
|
||||||
def unset_data(self, key: str):
|
|
||||||
if key == "":
|
|
||||||
self._data = {}
|
|
||||||
data = self._data
|
|
||||||
keys = key.split(".")
|
|
||||||
for k in keys[:-1]:
|
|
||||||
data = data[k]
|
|
||||||
del data[keys[-1]]
|
|
||||||
|
|
||||||
def get_deps(self) -> list[str]:
|
|
||||||
if self.parent is not None:
|
|
||||||
return self.parent.get_deps()
|
|
||||||
else:
|
|
||||||
return self.deps
|
|
||||||
|
|
||||||
def add_dep(self, dep: str):
|
|
||||||
self.get_deps().add(os.path.abspath(dep))
|
|
||||||
|
|
||||||
def add_deps(self, deps: list[str]):
|
|
||||||
self.get_deps().update([os.path.abspath(path) for path in deps])
|
|
||||||
|
|
||||||
|
|
||||||
def get_context_from_doc(doc: Doc) -> Context:
|
|
||||||
if len(doc.content) == 1 and isinstance(doc.content[0], Group):
|
|
||||||
return doc.content[0].context
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# This is a custom element which creates \begingroup \endgroup groups in TeX
|
# This is a custom element which creates \begingroup \endgroup groups in TeX
|
||||||
# and also causes KaTeX math blocks to be isolated in a similar way.
|
# and also causes KaTeX math blocks to be isolated in a similar way.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from panflute import Quoted, Link
|
from panflute import Quoted
|
||||||
|
|
||||||
|
|
||||||
from .command import Command, InlineCommand, BlockCommand, CodeCommand
|
from .command import Command, InlineCommand, BlockCommand, CodeCommand
|
||||||
|
@ -14,6 +14,3 @@ class FQuoted(Quoted):
|
||||||
del kwargs["style"]
|
del kwargs["style"]
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class FileLink(Link):
|
|
||||||
pass
|
|
||||||
|
|
|
@ -8,14 +8,14 @@ import shutil
|
||||||
|
|
||||||
# Import local files
|
# Import local files
|
||||||
from .util import import_md
|
from .util import import_md
|
||||||
|
from .context import Context, BlockGroup
|
||||||
from .katex import KatexClient
|
from .katex import KatexClient
|
||||||
from .images import ImageProcessor, ImageProcessorNamespace
|
from .images import ImageProcessor, ImageProcessorNamespace
|
||||||
from .output_generator import OutputGenerator, FormatitkoRecursiveError
|
from .output_generator import OutputGenerator, FormatitkoRecursiveError
|
||||||
from .html_generator import HTMLGenerator, StandaloneHTMLGenerator
|
from .html_generator import HTMLGenerator
|
||||||
from .transform_processor import TransformProcessor
|
from .transform_processor import TransformProcessor
|
||||||
from .pandoc_processor import PandocProcessor
|
from .pandoc_processor import PandocProcessor
|
||||||
from .tex_generator import UCWTexGenerator
|
from .tex_generator import UCWTexGenerator
|
||||||
from .context import get_context_from_doc
|
|
||||||
|
|
||||||
from panflute import convert_text
|
from panflute import convert_text
|
||||||
|
|
||||||
|
@ -27,7 +27,6 @@ def main():
|
||||||
parser.add_argument("-c", "--img-cache-dir", help="Directory to cache processed images and intermediate products. The program will overwrite files, whose dependencies are newer.", default="cache")
|
parser.add_argument("-c", "--img-cache-dir", help="Directory to cache processed images and intermediate products. The program will overwrite files, whose dependencies are newer.", default="cache")
|
||||||
parser.add_argument("-i", "--img-web-path", help="Path where the processed images are available on the website.", default="/")
|
parser.add_argument("-i", "--img-web-path", help="Path where the processed images are available on the website.", default="/")
|
||||||
parser.add_argument("-w", "--output-html", help="The HTML file (for Web) to write into.")
|
parser.add_argument("-w", "--output-html", help="The HTML file (for Web) to write into.")
|
||||||
parser.add_argument("-s", "--output-standalone-html", help="The Standalone HTML file to write into. A full page is generated instead of just a fragment.")
|
|
||||||
parser.add_argument("-t", "--output-tex", help="The TEX file to write into.")
|
parser.add_argument("-t", "--output-tex", help="The TEX file to write into.")
|
||||||
parser.add_argument("-m", "--output-md", help="The Markdown file to write into. (Uses pandoc to generate markdown)")
|
parser.add_argument("-m", "--output-md", help="The Markdown file to write into. (Uses pandoc to generate markdown)")
|
||||||
parser.add_argument("-j", "--output-json", help="The JSON file to dump the pandoc-compatible AST into.")
|
parser.add_argument("-j", "--output-json", help="The JSON file to dump the pandoc-compatible AST into.")
|
||||||
|
@ -36,8 +35,6 @@ def main():
|
||||||
parser.add_argument("-k", "--katex-socket", help="The KaTeX server socket filename obtained by running with `--katex-server`.")
|
parser.add_argument("-k", "--katex-socket", help="The KaTeX server socket filename obtained by running with `--katex-server`.")
|
||||||
parser.add_argument("input_filename", help="The markdown file to process.", nargs="?" if "--katex-server" in sys.argv else None)
|
parser.add_argument("input_filename", help="The markdown file to process.", nargs="?" if "--katex-server" in sys.argv else None)
|
||||||
parser.add_argument("--debug", action='store_true')
|
parser.add_argument("--debug", action='store_true')
|
||||||
parser.add_argument("--traceback-limit", help="Traceback limit for when errors happen, defaults to 0, as it is only useful for internal debugging.", default=0)
|
|
||||||
parser.add_argument("--deps", help="File to write list of dependencies to. May depend on output formats used.")
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.katex_server:
|
if args.katex_server:
|
||||||
|
@ -57,12 +54,12 @@ def main():
|
||||||
try:
|
try:
|
||||||
OutputGenerator(sys.stdout).generate(doc)
|
OutputGenerator(sys.stdout).generate(doc)
|
||||||
except FormatitkoRecursiveError as e:
|
except FormatitkoRecursiveError as e:
|
||||||
e.pretty_print(tracebacklimit=args.traceback_limit)
|
e.pretty_print()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
doc = TransformProcessor(args.input_filename).transform(doc)
|
doc = TransformProcessor(args.input_filename).transform(doc)
|
||||||
except FormatitkoRecursiveError as e:
|
except FormatitkoRecursiveError as e:
|
||||||
e.pretty_print(tracebacklimit=args.traceback_limit)
|
e.pretty_print()
|
||||||
|
|
||||||
# Initialize the image processor (this just keeps some basic state)
|
# Initialize the image processor (this just keeps some basic state)
|
||||||
imageProcessor = ImageProcessor({"": ImageProcessorNamespace(args.img_public_dir, args.img_web_path, args.img_cache_dir, args.img_lookup_dirs, True)})
|
imageProcessor = ImageProcessor({"": ImageProcessorNamespace(args.img_public_dir, args.img_web_path, args.img_cache_dir, args.img_lookup_dirs, True)})
|
||||||
|
@ -74,23 +71,15 @@ def main():
|
||||||
try:
|
try:
|
||||||
HTMLGenerator(file, katexClient, imageProcessor).generate(doc)
|
HTMLGenerator(file, katexClient, imageProcessor).generate(doc)
|
||||||
except FormatitkoRecursiveError as e:
|
except FormatitkoRecursiveError as e:
|
||||||
e.pretty_print(tracebacklimit=args.traceback_limit)
|
e.pretty_print()
|
||||||
|
|
||||||
if args.output_standalone_html is not None:
|
|
||||||
# Initialize KaTeX client (this runs the node app and connects to a unix socket)
|
|
||||||
with KatexClient(socket=args.katex_socket) as katexClient:
|
|
||||||
with open(args.output_standalone_html, "w") as file:
|
|
||||||
try:
|
|
||||||
StandaloneHTMLGenerator(file, katexClient, imageProcessor).generate(doc)
|
|
||||||
except FormatitkoRecursiveError as e:
|
|
||||||
e.pretty_print(tracebacklimit=args.traceback_limit)
|
|
||||||
|
|
||||||
if args.output_tex is not None:
|
if args.output_tex is not None:
|
||||||
with open(args.output_tex, "w") as file:
|
with open(args.output_tex, "w") as file:
|
||||||
try:
|
try:
|
||||||
UCWTexGenerator(file, imageProcessor).generate(doc)
|
UCWTexGenerator(file, imageProcessor).generate(doc)
|
||||||
except FormatitkoRecursiveError as e:
|
except FormatitkoRecursiveError as e:
|
||||||
e.pretty_print(tracebacklimit=args.traceback_limit)
|
e.pretty_print()
|
||||||
|
|
||||||
|
|
||||||
if args.output_md is not None:
|
if args.output_md is not None:
|
||||||
with open(args.output_md, "w") as file:
|
with open(args.output_md, "w") as file:
|
||||||
|
@ -107,7 +96,7 @@ def main():
|
||||||
try:
|
try:
|
||||||
UCWTexGenerator(file, imageProcessor).generate(doc)
|
UCWTexGenerator(file, imageProcessor).generate(doc)
|
||||||
except FormatitkoRecursiveError as e:
|
except FormatitkoRecursiveError as e:
|
||||||
e.pretty_print(tracebacklimit=args.traceback_limit)
|
e.pretty_print()
|
||||||
filename = fd.name
|
filename = fd.name
|
||||||
else:
|
else:
|
||||||
filename = args.output_tex
|
filename = args.output_tex
|
||||||
|
@ -115,17 +104,12 @@ def main():
|
||||||
subprocess.run(["pdfcsplain", "-halt-on-error", "-output-directory="+outdir.name, "-jobname=formatitko", filename], check=True)
|
subprocess.run(["pdfcsplain", "-halt-on-error", "-output-directory="+outdir.name, "-jobname=formatitko", filename], check=True)
|
||||||
shutil.move(outdir.name+"/formatitko.pdf", args.output_pdf)
|
shutil.move(outdir.name+"/formatitko.pdf", args.output_pdf)
|
||||||
|
|
||||||
if args.deps is not None:
|
|
||||||
with open(args.deps, "w") as file:
|
|
||||||
for dep in get_context_from_doc(doc).get_deps():
|
|
||||||
file.write(dep + "\n")
|
|
||||||
|
|
||||||
if args.debug:
|
if args.debug:
|
||||||
print("-----------------------------------")
|
print("-----------------------------------")
|
||||||
try:
|
try:
|
||||||
OutputGenerator(sys.stdout).generate(doc)
|
OutputGenerator(sys.stdout).generate(doc)
|
||||||
except FormatitkoRecursiveError as e:
|
except FormatitkoRecursiveError as e:
|
||||||
e.pretty_print(tracebacklimit=args.traceback_limit)
|
e.pretty_print()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -19,8 +19,6 @@ from .output_generator import OutputGenerator
|
||||||
from .katex import KatexClient
|
from .katex import KatexClient
|
||||||
from .images import ImageProcessor, ImageProcessorNamespaceSearcher
|
from .images import ImageProcessor, ImageProcessorNamespaceSearcher
|
||||||
from .util import inlinify
|
from .util import inlinify
|
||||||
from .elements import FileLink
|
|
||||||
|
|
||||||
|
|
||||||
class HTMLGenerator(OutputGenerator):
|
class HTMLGenerator(OutputGenerator):
|
||||||
imageProcessor: ImageProcessor
|
imageProcessor: ImageProcessor
|
||||||
|
@ -138,7 +136,6 @@ class HTMLGenerator(OutputGenerator):
|
||||||
url = e.url
|
url = e.url
|
||||||
|
|
||||||
additional_args = self.get_image_processor_args(e.attributes)
|
additional_args = self.get_image_processor_args(e.attributes)
|
||||||
additional_args["context"] = self.context
|
|
||||||
|
|
||||||
# The directory of the current file relative to the current working directory
|
# The directory of the current file relative to the current working directory
|
||||||
source_dir = self.context.dir
|
source_dir = self.context.dir
|
||||||
|
@ -146,7 +143,6 @@ class HTMLGenerator(OutputGenerator):
|
||||||
rel_dir = self.context.rel_dir
|
rel_dir = self.context.rel_dir
|
||||||
|
|
||||||
searcher = self.imageProcessor.get_searcher_by_path(url, rel_dir, source_dir)
|
searcher = self.imageProcessor.get_searcher_by_path(url, rel_dir, source_dir)
|
||||||
url = self.imageProcessor.get_path_without_namespace(url)
|
|
||||||
|
|
||||||
_, ext = os.path.splitext(url)
|
_, ext = os.path.splitext(url)
|
||||||
ext = ext[1:]
|
ext = ext[1:]
|
||||||
|
@ -193,10 +189,6 @@ class HTMLGenerator(OutputGenerator):
|
||||||
attributes = self.common_attributes(e)
|
attributes = self.common_attributes(e)
|
||||||
if "width" in e.attributes:
|
if "width" in e.attributes:
|
||||||
attributes["width"] = e.attributes["width"]
|
attributes["width"] = e.attributes["width"]
|
||||||
if "height" in e.attributes:
|
|
||||||
attributes["height"] = e.attributes["height"]
|
|
||||||
if "title" in e.attributes:
|
|
||||||
attributes["title"] = e.attributes["title"]
|
|
||||||
|
|
||||||
if e.title:
|
if e.title:
|
||||||
attributes["alt"] = e.title
|
attributes["alt"] = e.title
|
||||||
|
@ -205,38 +197,17 @@ class HTMLGenerator(OutputGenerator):
|
||||||
HTMLGenerator(fake_out, self.katexClient, self.imageProcessor).generate(e.content)
|
HTMLGenerator(fake_out, self.katexClient, self.imageProcessor).generate(e.content)
|
||||||
attributes["alt"] = fake_out.getvalue()
|
attributes["alt"] = fake_out.getvalue()
|
||||||
|
|
||||||
if len(srcset) > 1:
|
if len(srcset) != 0:
|
||||||
attributes["src"] = srcset[-1][0]
|
attributes["src"] = srcset[-1][0]
|
||||||
attributes["srcset"] = ", ".join([" ".join(src) for src in srcset])
|
attributes["srcset"] = ", ".join([" ".join(src) for src in srcset])
|
||||||
else:
|
else:
|
||||||
attributes["src"] = url
|
attributes["src"] = url
|
||||||
|
|
||||||
if e.attributes["no-img-link"]:
|
|
||||||
self.write(self.single_tag("img", attributes))
|
|
||||||
return
|
|
||||||
|
|
||||||
img = RawInline(self.single_tag("img", attributes))
|
img = RawInline(self.single_tag("img", attributes))
|
||||||
link = Link(img, url=url)
|
link = Link(img, url=url)
|
||||||
|
|
||||||
self.generate(link)
|
self.generate(link)
|
||||||
|
|
||||||
def generate_FileLink(self, e: FileLink):
|
|
||||||
url = e.url
|
|
||||||
|
|
||||||
# The directory of the current file relative to the current working directory
|
|
||||||
source_dir = self.context.dir
|
|
||||||
# The directory of the current file relative to the md file we were called on
|
|
||||||
rel_dir = self.context.rel_dir
|
|
||||||
|
|
||||||
searcher = self.imageProcessor.get_searcher_by_path(url, rel_dir, source_dir)
|
|
||||||
url = self.imageProcessor.get_path_without_namespace(url)
|
|
||||||
|
|
||||||
url = self.imageProcessor.process_image(url, "", searcher, self.context)
|
|
||||||
searcher.publish_image(url)
|
|
||||||
url = searcher.get_web_path() + "/" + url
|
|
||||||
|
|
||||||
self.generate_Link(Link(*e.content, url=url))
|
|
||||||
|
|
||||||
def generate_InlineGroup(self, e: InlineGroup):
|
def generate_InlineGroup(self, e: InlineGroup):
|
||||||
self.generate_Group(e)
|
self.generate_Group(e)
|
||||||
|
|
||||||
|
@ -343,34 +314,3 @@ class HTMLGenerator(OutputGenerator):
|
||||||
|
|
||||||
def generate_DefinitionList(self, e: DefinitionList):
|
def generate_DefinitionList(self, e: DefinitionList):
|
||||||
self.writeln("<!-- FIXME: DefinitionLists not implemented -->")
|
self.writeln("<!-- FIXME: DefinitionLists not implemented -->")
|
||||||
|
|
||||||
|
|
||||||
class StandaloneHTMLGenerator(HTMLGenerator):
|
|
||||||
def generate_Doc(self, e: Doc):
|
|
||||||
self.writeraw("<!DOCTYPE html>")
|
|
||||||
self.writeln(self.start_tag("html", attributes={"lang": e.get_metadata("lang", None, True)}))
|
|
||||||
self.writeln(self.start_tag("head"))
|
|
||||||
self.indent_more()
|
|
||||||
self.writeln(self.single_tag("meta", attributes={"charset": "utf-8"}))
|
|
||||||
self.writeln(self.single_tag("meta", attributes={"viewport": "width=device-width, initial-scale=1.0"}))
|
|
||||||
self.writeln(self.single_tag("link", attributes={"rel": "stylesheet", "href": "https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css", "integrity":"sha384-vKruj+a13U8yHIkAyGgK1J3ArTLzrFGBbBc0tDp4ad/EyewESeXE/Iv67Aj8gKZ0", "crossorigin":"anonymous"}))
|
|
||||||
if "title" in e.metadata:
|
|
||||||
self.write(self.start_tag("title"))
|
|
||||||
self.generate(e.metadata["title"])
|
|
||||||
self.write(self.end_tag("title"))
|
|
||||||
self.endln()
|
|
||||||
if "html-head-includes" in e.metadata:
|
|
||||||
self.generate(e.metadata["html-head-includes"])
|
|
||||||
self.indent_less()
|
|
||||||
self.writeln(self.end_tag("head"))
|
|
||||||
self.writeln(self.start_tag("body"))
|
|
||||||
self.indent_more()
|
|
||||||
super().generate_Doc(e)
|
|
||||||
self.indent_less()
|
|
||||||
self.writeln(self.end_tag("body"))
|
|
||||||
self.writeln(self.end_tag("html"))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,6 @@ import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from .context import Context
|
|
||||||
|
|
||||||
|
|
||||||
class FileInWrongDirError(Exception):
|
class FileInWrongDirError(Exception):
|
||||||
pass
|
pass
|
||||||
|
@ -40,6 +38,11 @@ class ImageProcessorNamespace:
|
||||||
self.lookup_dirs = lookup_dirs
|
self.lookup_dirs = lookup_dirs
|
||||||
self.web_path = web_path if web_path[-1] != "/" else web_path[:-1]
|
self.web_path = web_path if web_path[-1] != "/" else web_path[:-1]
|
||||||
self.include_src = include_src
|
self.include_src = include_src
|
||||||
|
if not os.path.exists(self.public_dir):
|
||||||
|
os.mkdir(self.public_dir)
|
||||||
|
if not os.path.exists(self.cache_dir):
|
||||||
|
os.mkdir(self.cache_dir)
|
||||||
|
|
||||||
|
|
||||||
class ImageProcessorSearcher:
|
class ImageProcessorSearcher:
|
||||||
def get_lookup_dirs(self) -> list[str]:
|
def get_lookup_dirs(self) -> list[str]:
|
||||||
|
@ -93,8 +96,6 @@ class ImageProcessorCacheSearcher(ImageProcessorSearcher):
|
||||||
|
|
||||||
def __init__(self, cache_dir: str):
|
def __init__(self, cache_dir: str):
|
||||||
self.cache_dir = cache_dir
|
self.cache_dir = cache_dir
|
||||||
if not os.path.exists(self.cache_dir):
|
|
||||||
os.makedirs(self.cache_dir, exist_ok=True)
|
|
||||||
|
|
||||||
def get_lookup_dirs(self) -> list[str]:
|
def get_lookup_dirs(self) -> list[str]:
|
||||||
return [self.cache_dir]
|
return [self.cache_dir]
|
||||||
|
@ -128,16 +129,10 @@ class ImageProcessorNamespaceSearcher(ImageProcessorSearcher):
|
||||||
return path.replace("$dir", self.rel_dir)
|
return path.replace("$dir", self.rel_dir)
|
||||||
|
|
||||||
def get_cache_dir(self) -> str:
|
def get_cache_dir(self) -> str:
|
||||||
cache_dir = self.transform_path(self.namespace.cache_dir)
|
return self.transform_path(self.namespace.cache_dir)
|
||||||
if not os.path.exists(cache_dir):
|
|
||||||
os.makedirs(cache_dir, exist_ok=True)
|
|
||||||
return cache_dir
|
|
||||||
|
|
||||||
def get_public_dir(self) -> str:
|
def get_public_dir(self) -> str:
|
||||||
public_dir = self.transform_path(self.namespace.public_dir)
|
return self.transform_path(self.namespace.public_dir)
|
||||||
if not os.path.exists(public_dir):
|
|
||||||
os.makedirs(public_dir, exist_ok=True)
|
|
||||||
return public_dir
|
|
||||||
|
|
||||||
def get_web_path(self) -> str:
|
def get_web_path(self) -> str:
|
||||||
return self.transform_path(self.namespace.web_path)
|
return self.transform_path(self.namespace.web_path)
|
||||||
|
@ -154,15 +149,10 @@ class ImageProcessor:
|
||||||
def get_namespace_by_path(self, path: str) -> ImageProcessorNamespace:
|
def get_namespace_by_path(self, path: str) -> ImageProcessorNamespace:
|
||||||
return self.namespaces[path.split(":")[0] if ":" in path else ""]
|
return self.namespaces[path.split(":")[0] if ":" in path else ""]
|
||||||
|
|
||||||
def get_path_without_namespace(self, path: str) -> str:
|
|
||||||
if len(path.split(":")) <= 1:
|
|
||||||
return path
|
|
||||||
return ":".join(path.split(":")[1:])
|
|
||||||
|
|
||||||
def get_searcher_by_path(self, path: str, rel_dir: str, source_dir: str) -> ImageProcessorNamespaceSearcher:
|
def get_searcher_by_path(self, path: str, rel_dir: str, source_dir: str) -> ImageProcessorNamespaceSearcher:
|
||||||
return ImageProcessorNamespaceSearcher(self.get_namespace_by_path(path), rel_dir, source_dir)
|
return ImageProcessorNamespaceSearcher(self.get_namespace_by_path(path), rel_dir, source_dir)
|
||||||
|
|
||||||
def process_image(self, input_filename: str, format: str, searcher: ImageProcessorSearcher, context: Context=None, width: int=None, height:int=None, quality: int=None, dpi: int=None, fit: bool=True, deps: list[str]=[]) -> str:
|
def process_image(self, input_filename: str, format: str, searcher: ImageProcessorSearcher, width: int=None, height:int=None, quality: int=None, dpi: int=None, fit: bool=True, deps: list[str]=[]) -> str:
|
||||||
name = os.path.basename(input_filename)
|
name = os.path.basename(input_filename)
|
||||||
base, ext = os.path.splitext(name)
|
base, ext = os.path.splitext(name)
|
||||||
ext = ext[1:]
|
ext = ext[1:]
|
||||||
|
@ -173,9 +163,6 @@ class ImageProcessor:
|
||||||
if format == "jpg":
|
if format == "jpg":
|
||||||
format = "jpeg"
|
format = "jpeg"
|
||||||
|
|
||||||
if format == "":
|
|
||||||
format = ext
|
|
||||||
|
|
||||||
# Locate all dependencies
|
# Locate all dependencies
|
||||||
deps_full = [full_path]
|
deps_full = [full_path]
|
||||||
for dep in deps:
|
for dep in deps:
|
||||||
|
@ -242,8 +229,6 @@ class ImageProcessor:
|
||||||
if subprocess.run(['convert', *density_arg, full_path, *resize_arg, *quality_arg, target_path]).returncode != 0:
|
if subprocess.run(['convert', *density_arg, full_path, *resize_arg, *quality_arg, target_path]).returncode != 0:
|
||||||
raise ImageMagickError(f"Could not convert '{full_path}' to '{format}'")
|
raise ImageMagickError(f"Could not convert '{full_path}' to '{format}'")
|
||||||
|
|
||||||
if context is not None:
|
|
||||||
context.add_deps(deps_full)
|
|
||||||
return target_name
|
return target_name
|
||||||
|
|
||||||
def is_outdated(self, target: str, deps: list[str]):
|
def is_outdated(self, target: str, deps: list[str]):
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 211cb2010e23265be599819c5f79f66f0abd62d1
|
|
1
src/formatitko/katex-server/.gitignore
vendored
Normal file
1
src/formatitko/katex-server/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
node_modules
|
1
src/formatitko/katex-server/README.md
Normal file
1
src/formatitko/katex-server/README.md
Normal file
|
@ -0,0 +1 @@
|
||||||
|
This was made by Standa Lukeš @exyi
|
1
src/formatitko/katex-server/index.js
Normal file
1
src/formatitko/katex-server/index.js
Normal file
|
@ -0,0 +1 @@
|
||||||
|
console.log(require('katex').renderToString('\\frac{2a}{b}'))
|
131
src/formatitko/katex-server/index.mjs
Normal file
131
src/formatitko/katex-server/index.mjs
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
// KaTeX rendering server
|
||||||
|
// Listens on unix socket, path is provided as first argument
|
||||||
|
// Expects JSON lines, each line is a query with the following schema:
|
||||||
|
// {
|
||||||
|
// formulas: [
|
||||||
|
// {
|
||||||
|
// tex: string,
|
||||||
|
// options?: object
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// options?: object
|
||||||
|
// }
|
||||||
|
|
||||||
|
// see https://katex.org/docs/options.html for list of available options
|
||||||
|
// If options formulas[].options field is used, the global options field is ignored.
|
||||||
|
|
||||||
|
// For each line, returns one JSON line with the following schema:
|
||||||
|
// {
|
||||||
|
// results: [
|
||||||
|
// { html?: string } | { error?: string }
|
||||||
|
// ]
|
||||||
|
// } | { error?: string }
|
||||||
|
|
||||||
|
|
||||||
|
// If one formula is invalid, the error in results is used
|
||||||
|
// If the entire query is invalid (couldn't parse JSON, for example), the outer error field is used
|
||||||
|
|
||||||
|
|
||||||
|
import katex from 'katex'
|
||||||
|
import net from 'net'
|
||||||
|
import * as readline from 'readline'
|
||||||
|
|
||||||
|
const myArgs = process.argv.slice(2)
|
||||||
|
|
||||||
|
const unixSocketPath = myArgs[0]
|
||||||
|
if (!unixSocketPath) {
|
||||||
|
console.error('you must specify socket path')
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This server listens on a Unix socket at /var/run/mysocket
|
||||||
|
var unixServer = net.createServer(handleClient);
|
||||||
|
unixServer.listen(unixSocketPath);
|
||||||
|
console.log("OK")
|
||||||
|
|
||||||
|
function handleExit(signal) {
|
||||||
|
// unixServer.emit('close')
|
||||||
|
unixServer.close(function () {
|
||||||
|
|
||||||
|
});
|
||||||
|
process.exit(0); // put this into the callback to avoid closing open connections
|
||||||
|
}
|
||||||
|
process.on('SIGINT', handleExit);
|
||||||
|
process.on('SIGQUIT', handleExit);
|
||||||
|
process.on('SIGTERM', handleExit);
|
||||||
|
process.on('exit', handleExit);
|
||||||
|
|
||||||
|
const defaultOptions = {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {net.Socket} socket
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
* */
|
||||||
|
function socketWrite(socket, data) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
socket.write(data, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
} else {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {net.Socket} client
|
||||||
|
* */
|
||||||
|
async function handleClient(client) {
|
||||||
|
const rl = readline.createInterface({ input: client })
|
||||||
|
|
||||||
|
/* Added by GS: A stack of katex's `macros` objects, each group inherits
|
||||||
|
* the one from the parent group and can add its own stuff without
|
||||||
|
* affecting the parent.
|
||||||
|
*/
|
||||||
|
let macroStack = [{}]
|
||||||
|
for await (const line of rl) {
|
||||||
|
try {
|
||||||
|
// The custom commands for pushing and popping the macro stack.
|
||||||
|
if (line === "begingroup") {
|
||||||
|
// Copy the current state of macros and push it onto the stack.
|
||||||
|
macroStack.push({...macroStack.slice(-1)[0]})
|
||||||
|
continue
|
||||||
|
} else if (line === "endgroup") {
|
||||||
|
macroStack.pop()
|
||||||
|
continue
|
||||||
|
} else if (line === "init") {
|
||||||
|
macroStack = [{}]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const query = JSON.parse(line)
|
||||||
|
const results = []
|
||||||
|
for (const input of query.formulas) {
|
||||||
|
const options = input.options ?? query.options ?? defaultOptions
|
||||||
|
// Add macros from the macros option
|
||||||
|
if (options.macros) {
|
||||||
|
for (const macro of Object.keys(options.macros)) {
|
||||||
|
macroStack.slice(-1)[macro] = options.macros[macro]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
options.macros = macroStack.slice(-1)[0]
|
||||||
|
// Enforce globalGroup option, katex then saves created macros
|
||||||
|
// into the options.macros object.
|
||||||
|
options.globalGroup = true
|
||||||
|
try {
|
||||||
|
const html = katex.renderToString(input.tex, options)
|
||||||
|
results.push({ html })
|
||||||
|
} catch (e) {
|
||||||
|
results.push({ error: String(e) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await socketWrite(client, JSON.stringify({ results }, null, query.debug ? ' ' : undefined))
|
||||||
|
await socketWrite(client, '\n')
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
await socketWrite(client, JSON.stringify({ error: String(e) }))
|
||||||
|
await socketWrite(client, '\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
39
src/formatitko/katex-server/package-lock.json
generated
Normal file
39
src/formatitko/katex-server/package-lock.json
generated
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"name": "ksp-katex-server",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "ksp-katex-server",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"katex": "^0.16.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/commander": {
|
||||||
|
"version": "8.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
|
||||||
|
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/katex": {
|
||||||
|
"version": "0.16.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.3.tgz",
|
||||||
|
"integrity": "sha512-3EykQddareoRmbtNiNEDgl3IGjryyrp2eg/25fHDEnlHymIDi33bptkMv6K4EOC2LZCybLW/ZkEo6Le+EM9pmA==",
|
||||||
|
"funding": [
|
||||||
|
"https://opencollective.com/katex",
|
||||||
|
"https://github.com/sponsors/katex"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"commander": "^8.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"katex": "cli.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
src/formatitko/katex-server/package.json
Normal file
14
src/formatitko/katex-server/package.json
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"name": "ksp-katex-server",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.mjs",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"katex": "^0.16.3"
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,6 @@ import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import shutil
|
|
||||||
|
|
||||||
class KatexError(Exception):
|
class KatexError(Exception):
|
||||||
pass
|
pass
|
||||||
|
@ -21,10 +20,8 @@ class KatexClient:
|
||||||
_socket_file: str
|
_socket_file: str
|
||||||
_temp_dir: tempfile.TemporaryDirectory[str]
|
_temp_dir: tempfile.TemporaryDirectory[str]
|
||||||
_connected: bool
|
_connected: bool
|
||||||
_katex_server_path: str
|
|
||||||
|
|
||||||
def __init__(self, socket: str=None, connect: bool=True, katex_server_path: str=None):
|
def __init__(self, socket: str=None, connect: bool=True):
|
||||||
self._katex_server_path = katex_server_path
|
|
||||||
if socket is not None:
|
if socket is not None:
|
||||||
self._socket_file = socket
|
self._socket_file = socket
|
||||||
else:
|
else:
|
||||||
|
@ -41,21 +38,20 @@ class KatexClient:
|
||||||
self._temp_dir = tempfile.TemporaryDirectory(prefix='formatitko')
|
self._temp_dir = tempfile.TemporaryDirectory(prefix='formatitko')
|
||||||
self._socket_file = self._temp_dir.name + "/katex-socket"
|
self._socket_file = self._temp_dir.name + "/katex-socket"
|
||||||
|
|
||||||
if self._katex_server_path is None:
|
srcdir = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|
||||||
srcdir = os.path.dirname(os.path.realpath(__file__))
|
|
||||||
|
|
||||||
# Test if `node_modules` directory exists and if not, run `npm install`
|
# Test if `node_modules` directory exists and if not, run `npm install`
|
||||||
if not os.path.isdir(srcdir + "/katex-server/node_modules"):
|
if not os.path.isdir(srcdir + "/katex-server/node_modules"):
|
||||||
print("Installing node dependencies for the first time...")
|
print("Installing node dependencies for the first time...")
|
||||||
npm = shutil.which("npm") or shutil.which("yarnpkg")
|
try:
|
||||||
if npm is None:
|
subprocess.run(["npm", "install"], cwd=srcdir+"/katex-server", check=True)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
if e.returncode == 127:
|
||||||
raise NPMNotFoundError("npm not found. Node.js is required to use KaTeX.")
|
raise NPMNotFoundError("npm not found. Node.js is required to use KaTeX.")
|
||||||
subprocess.run([npm, "install"], cwd=srcdir+"/katex-server", check=True)
|
else:
|
||||||
|
raise e
|
||||||
self._katex_server_path = srcdir + "/katex-server/index.mjs"
|
|
||||||
|
|
||||||
self._server_process = subprocess.Popen(["node", self._katex_server_path, self._socket_file], stdout=subprocess.PIPE)
|
self._server_process = subprocess.Popen(["node", srcdir + "/katex-server/index.mjs", self._socket_file], stdout=subprocess.PIPE)
|
||||||
|
|
||||||
ok = self._server_process.stdout.readline()
|
ok = self._server_process.stdout.readline()
|
||||||
if ok != b"OK\n":
|
if ok != b"OK\n":
|
||||||
|
|
|
@ -6,7 +6,7 @@ from panflute import MetaValue
|
||||||
from typing import Union, Callable
|
from typing import Union, Callable
|
||||||
|
|
||||||
from .whitespace import NBSP
|
from .whitespace import NBSP
|
||||||
from .elements import FQuoted, FileLink
|
from .elements import FQuoted
|
||||||
from .context import Group, InlineGroup, BlockGroup, Context
|
from .context import Group, InlineGroup, BlockGroup, Context
|
||||||
from .whitespace import Whitespace
|
from .whitespace import Whitespace
|
||||||
from .command import BlockCommand, InlineCommand, CodeCommand, Command
|
from .command import BlockCommand, InlineCommand, CodeCommand, Command
|
||||||
|
@ -88,7 +88,6 @@ class NOPProcessor:
|
||||||
Underline: self.transform_Underline,
|
Underline: self.transform_Underline,
|
||||||
NBSP: self.transform_NBSP,
|
NBSP: self.transform_NBSP,
|
||||||
FQuoted: self.transform_FQuoted,
|
FQuoted: self.transform_FQuoted,
|
||||||
FileLink: self.transform_FileLink,
|
|
||||||
|
|
||||||
InlineCommand: self.transform_InlineCommand,
|
InlineCommand: self.transform_InlineCommand,
|
||||||
BlockCommand: self.transform_BlockCommand,
|
BlockCommand: self.transform_BlockCommand,
|
||||||
|
@ -112,12 +111,10 @@ class NOPProcessor:
|
||||||
e = transformer(e)
|
e = transformer(e)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
method = self.TYPE_DICT[type(e)]
|
e = self.TYPE_DICT[type(e)](e)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise self.UnknownElementError(type(e))
|
raise self.UnknownElementError(type(e))
|
||||||
|
|
||||||
e = method(e)
|
|
||||||
|
|
||||||
for transformer in self.get_posttransformers():
|
for transformer in self.get_posttransformers():
|
||||||
e = transformer(e)
|
e = transformer(e)
|
||||||
|
|
||||||
|
@ -130,17 +127,13 @@ class NOPProcessor:
|
||||||
raise FormatitkoRecursiveError(e, self.context) from err
|
raise FormatitkoRecursiveError(e, self.context) from err
|
||||||
|
|
||||||
def transform_list(self, e: list[Union[Element, ListContainer]]) -> list[Union[Element, ListContainer]]:
|
def transform_list(self, e: list[Union[Element, ListContainer]]) -> list[Union[Element, ListContainer]]:
|
||||||
i = 0
|
for i in range(len(e)):
|
||||||
while i < len(e): # The length of the list can change mid-transformation, so we need to check the length each time
|
|
||||||
e[i] = self.transform(e[i])
|
e[i] = self.transform(e[i])
|
||||||
i-=-1
|
|
||||||
return e
|
return e
|
||||||
|
|
||||||
def transform_ListContainer(self, e: ListContainer) -> ListContainer:
|
def transform_ListContainer(self, e: ListContainer) -> ListContainer:
|
||||||
i = 0
|
for i in range(len(e)):
|
||||||
while i < len(e): # The length of the list can change mid-transformation, so we need to check the length each time
|
|
||||||
e[i] = self.transform(e[i])
|
e[i] = self.transform(e[i])
|
||||||
i-=-1
|
|
||||||
return e
|
return e
|
||||||
|
|
||||||
|
|
||||||
|
@ -300,10 +293,6 @@ class NOPProcessor:
|
||||||
e.content = self.transform(e.content)
|
e.content = self.transform(e.content)
|
||||||
return e
|
return e
|
||||||
|
|
||||||
def transform_FileLink(self, e: FileLink) -> FileLink:
|
|
||||||
e.content = self.transform(e.content)
|
|
||||||
return e
|
|
||||||
|
|
||||||
def transform_Figure(self, e: Figure) -> Figure:
|
def transform_Figure(self, e: Figure) -> Figure:
|
||||||
e.content = self.transform(e.content)
|
e.content = self.transform(e.content)
|
||||||
e.caption = self.transform(e.caption)
|
e.caption = self.transform(e.caption)
|
||||||
|
|
|
@ -7,7 +7,7 @@ from panflute import stringify
|
||||||
from typing import Union, Callable
|
from typing import Union, Callable
|
||||||
|
|
||||||
from .whitespace import NBSP
|
from .whitespace import NBSP
|
||||||
from .elements import FQuoted, FileLink
|
from .elements import FQuoted
|
||||||
from .context import Group, InlineGroup, BlockGroup, Context
|
from .context import Group, InlineGroup, BlockGroup, Context
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ class FormatitkoRecursiveError(Exception):
|
||||||
def add_element(self, e: Union[Element, ListContainer, list[Union[Element, ListContainer]]]):
|
def add_element(self, e: Union[Element, ListContainer, list[Union[Element, ListContainer]]]):
|
||||||
self.elements.append(e)
|
self.elements.append(e)
|
||||||
|
|
||||||
def pretty_print(self, tracebacklimit: int=0):
|
def pretty_print(self):
|
||||||
def eprint(*args, **kwargs):
|
def eprint(*args, **kwargs):
|
||||||
print(*args, file=sys.stderr, **kwargs)
|
print(*args, file=sys.stderr, **kwargs)
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ class FormatitkoRecursiveError(Exception):
|
||||||
eprint('on line: "' + stringify(line).strip() + '"', end="")
|
eprint('on line: "' + stringify(line).strip() + '"', end="")
|
||||||
eprint()
|
eprint()
|
||||||
eprint("in element: " + str(self.elements[0]).replace("\n", "\\n"))
|
eprint("in element: " + str(self.elements[0]).replace("\n", "\\n"))
|
||||||
sys.tracebacklimit = tracebacklimit
|
sys.tracebacklimit = 0
|
||||||
raise self.__cause__ from None
|
raise self.__cause__ from None
|
||||||
|
|
||||||
|
|
||||||
|
@ -127,7 +127,6 @@ class OutputGenerator:
|
||||||
Underline: self.generate_Underline,
|
Underline: self.generate_Underline,
|
||||||
NBSP: self.generate_NBSP,
|
NBSP: self.generate_NBSP,
|
||||||
FQuoted: self.generate_FQuoted,
|
FQuoted: self.generate_FQuoted,
|
||||||
FileLink: self.generate_FileLink,
|
|
||||||
InlineGroup: self.generate_InlineGroup
|
InlineGroup: self.generate_InlineGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,10 +157,9 @@ class OutputGenerator:
|
||||||
self.generate_MetaList(e)
|
self.generate_MetaList(e)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
method = self.TYPE_DICT_MISC[type(e)]
|
self.TYPE_DICT_MISC[type(e)](e)
|
||||||
except KeyError as err:
|
except KeyError as err:
|
||||||
raise UnknownElementError(type(e)) from err
|
raise UnknownElementError(type(e)) from err
|
||||||
method(e)
|
|
||||||
if isinstance(e, Group):
|
if isinstance(e, Group):
|
||||||
self.context = old_context
|
self.context = old_context
|
||||||
except FormatitkoRecursiveError as err:
|
except FormatitkoRecursiveError as err:
|
||||||
|
@ -273,9 +271,9 @@ class OutputGenerator:
|
||||||
self.write(self.end_tag(tag))
|
self.write(self.end_tag(tag))
|
||||||
|
|
||||||
def generate_raw_block_tag(self, tag: str, text: str, attributes: dict[str,str]={}):
|
def generate_raw_block_tag(self, tag: str, text: str, attributes: dict[str,str]={}):
|
||||||
self.writeraw(self.start_tag(tag, attributes))
|
self.writeln(self.start_tag(tag, attributes))
|
||||||
self.writeraw(text)
|
self.writeraw(text)
|
||||||
self.writeraw(self.end_tag(tag))
|
self.writeln(self.end_tag(tag))
|
||||||
|
|
||||||
def generate_empty_block_tag(self, tag: str, attributes: dict[str,str]={}):
|
def generate_empty_block_tag(self, tag: str, attributes: dict[str,str]={}):
|
||||||
self.writeln(self.single_tag(tag, attributes))
|
self.writeln(self.single_tag(tag, attributes))
|
||||||
|
@ -294,10 +292,9 @@ class OutputGenerator:
|
||||||
|
|
||||||
def generate_MetaValue(self, e: MetaValue):
|
def generate_MetaValue(self, e: MetaValue):
|
||||||
try:
|
try:
|
||||||
method = self.TYPE_DICT_META[type(e)]
|
self.TYPE_DICT_META[type(e)](e)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.generate(e.content)
|
self.generate(e.content)
|
||||||
method(e)
|
|
||||||
|
|
||||||
def generate_MetaBlocks(self, e: MetaBlocks):
|
def generate_MetaBlocks(self, e: MetaBlocks):
|
||||||
self.generate(e.content)
|
self.generate(e.content)
|
||||||
|
@ -306,23 +303,16 @@ class OutputGenerator:
|
||||||
self.generate(e.content)
|
self.generate(e.content)
|
||||||
|
|
||||||
def generate_MetaBool(self, e: MetaBool):
|
def generate_MetaBool(self, e: MetaBool):
|
||||||
if e.boolean:
|
self.generate_simple_tag(e)
|
||||||
self.write("True")
|
|
||||||
else:
|
|
||||||
self.write("False")
|
|
||||||
|
|
||||||
def generate_MetaMap(self, e: MetaMap):
|
def generate_MetaMap(self, e: MetaMap):
|
||||||
self.generate_simple_tag(e)
|
self.generate_simple_tag(e)
|
||||||
|
|
||||||
def generate_MetaString(self, e: MetaString):
|
def generate_MetaString(self, e: MetaString):
|
||||||
self.write(e.text)
|
self.generate_simple_tag(e)
|
||||||
|
|
||||||
def generate_Inline(self, e: Inline):
|
def generate_Inline(self, e: Inline):
|
||||||
try:
|
self.TYPE_DICT_INLINE[type(e)](e)
|
||||||
method = self.TYPE_DICT_INLINE[type(e)]
|
|
||||||
except KeyError as err:
|
|
||||||
raise UnknownElementError(type(e)) from err
|
|
||||||
method(e)
|
|
||||||
|
|
||||||
def generate_Str(self, e: Str):
|
def generate_Str(self, e: Str):
|
||||||
self.write(self.escape_special_chars(e.text))
|
self.write(self.escape_special_chars(e.text))
|
||||||
|
@ -368,9 +358,7 @@ class OutputGenerator:
|
||||||
self.write("\"")
|
self.write("\"")
|
||||||
self.generate(e.content)
|
self.generate(e.content)
|
||||||
self.write("\"")
|
self.write("\"")
|
||||||
|
|
||||||
def generate_FileLink(self, e: FileLink):
|
|
||||||
self.generate_simple_tag(e)
|
|
||||||
|
|
||||||
# Inline Elements
|
# Inline Elements
|
||||||
def generate_Cite(self, e: Cite):
|
def generate_Cite(self, e: Cite):
|
||||||
|
@ -425,11 +413,7 @@ class OutputGenerator:
|
||||||
|
|
||||||
|
|
||||||
def generate_Block(self, e: Block):
|
def generate_Block(self, e: Block):
|
||||||
try:
|
self.TYPE_DICT_BLOCK[type(e)](e)
|
||||||
method = self.TYPE_DICT_BLOCK[type(e)]
|
|
||||||
except KeyError as err:
|
|
||||||
raise UnknownElementError(type(e)) from err
|
|
||||||
method(e)
|
|
||||||
|
|
||||||
|
|
||||||
# Block elements
|
# Block elements
|
||||||
|
@ -494,14 +478,12 @@ class OutputGenerator:
|
||||||
self.generate_simple_tag(e)
|
self.generate_simple_tag(e)
|
||||||
|
|
||||||
def generate_Doc(self, e: Doc):
|
def generate_Doc(self, e: Doc):
|
||||||
if "header-includes" in e.metadata: # This is the pandoc way of doing things
|
|
||||||
self.generate(e.metadata["header-includes"])
|
|
||||||
if "header_content" in e.metadata:
|
if "header_content" in e.metadata:
|
||||||
self.generate(e.metadata["header_content"])
|
self.generate(e.metadata["header_content"])
|
||||||
self.generate_simple_tag(e)
|
self.generate_simple_tag(e)
|
||||||
if "footer_content" in e.metadata:
|
if "footer_content" in e.metadata:
|
||||||
self.generate(e.metadata["footer_content"])
|
self.generate(e.metadata["footer_content"])
|
||||||
|
|
||||||
def generate_BlockGroup(self, e: BlockGroup):
|
def generate_BlockGroup(self, e: BlockGroup):
|
||||||
self.generate_simple_tag(e)
|
self.generate_simple_tag(e)
|
||||||
|
|
||||||
|
|
|
@ -110,7 +110,6 @@ class UCWTexGenerator(OutputGenerator):
|
||||||
url = e.url
|
url = e.url
|
||||||
|
|
||||||
additional_args = self.get_image_processor_args(e.attributes)
|
additional_args = self.get_image_processor_args(e.attributes)
|
||||||
additional_args["context"] = self.context
|
|
||||||
|
|
||||||
# The directory of the current file relative to the current working directory
|
# The directory of the current file relative to the current working directory
|
||||||
source_dir = self.context.dir
|
source_dir = self.context.dir
|
||||||
|
@ -118,7 +117,6 @@ class UCWTexGenerator(OutputGenerator):
|
||||||
rel_dir = self.context.rel_dir
|
rel_dir = self.context.rel_dir
|
||||||
|
|
||||||
searcher = self.imageProcessor.get_searcher_by_path(url, rel_dir, source_dir)
|
searcher = self.imageProcessor.get_searcher_by_path(url, rel_dir, source_dir)
|
||||||
url = self.imageProcessor.get_path_without_namespace(url)
|
|
||||||
|
|
||||||
_, ext = os.path.splitext(url)
|
_, ext = os.path.splitext(url)
|
||||||
ext = ext[1:]
|
ext = ext[1:]
|
||||||
|
|
|
@ -3,7 +3,6 @@ from panflute import Cite, Code, Emph, Image, LineBreak, Link, Math, Note, Quote
|
||||||
from panflute import BlockQuote, BulletList, Citation, CodeBlock, Definition, DefinitionItem, DefinitionList, Div, Figure, Header, HorizontalRule, LineBlock, LineItem, ListItem, MetaBlocks, MetaBool, MetaInlines, MetaList, MetaMap, MetaString, Null, OrderedList, Para, Plain, RawBlock, Table, TableBody, TableFoot, TableHead
|
from panflute import BlockQuote, BulletList, Citation, CodeBlock, Definition, DefinitionItem, DefinitionList, Div, Figure, Header, HorizontalRule, LineBlock, LineItem, ListItem, MetaBlocks, MetaBool, MetaInlines, MetaList, MetaMap, MetaString, Null, OrderedList, Para, Plain, RawBlock, Table, TableBody, TableFoot, TableHead
|
||||||
from panflute import TableRow, TableCell, Caption, Doc
|
from panflute import TableRow, TableCell, Caption, Doc
|
||||||
from panflute import MetaValue
|
from panflute import MetaValue
|
||||||
from panflute.containers import attach
|
|
||||||
from typing import Union, Callable
|
from typing import Union, Callable
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
|
|
||||||
|
@ -19,7 +18,7 @@ from .context import Group, InlineGroup, BlockGroup
|
||||||
from .util import nullify, import_md
|
from .util import nullify, import_md
|
||||||
from .context import Context, CommandCallable
|
from .context import Context, CommandCallable
|
||||||
from .whitespace import Whitespace, bavlna
|
from .whitespace import Whitespace, bavlna
|
||||||
from .command import BlockCommand, InlineCommand, CodeCommand, Command, InlineError
|
from .command import BlockCommand, InlineCommand, CodeCommand, Command
|
||||||
from .command_util import handle_command_define, parse_command
|
from .command_util import handle_command_define, parse_command
|
||||||
from .nop_processor import NOPProcessor, ELCl, DoubleDocError
|
from .nop_processor import NOPProcessor, ELCl, DoubleDocError
|
||||||
|
|
||||||
|
@ -40,15 +39,6 @@ class TransformProcessor(NOPProcessor):
|
||||||
def add_command_module(self, module: Union[dict[str, CommandCallable], ModuleType], module_name: str=""):
|
def add_command_module(self, module: Union[dict[str, CommandCallable], ModuleType], module_name: str=""):
|
||||||
self._command_modules.append((module, module_name))
|
self._command_modules.append((module, module_name))
|
||||||
|
|
||||||
def init_context(self, e: Doc) -> Context:
|
|
||||||
if self.context is not None:
|
|
||||||
raise DoubleDocError()
|
|
||||||
self.context = Context(e, self.root_file_path)
|
|
||||||
for module, module_name in self._command_modules:
|
|
||||||
self.context.add_commands_from_module(module, module_name)
|
|
||||||
e.content = [BlockGroup(*e.content, context=self.context)]
|
|
||||||
return self.context
|
|
||||||
|
|
||||||
def get_pretransformers(self) -> list[Callable[[ELCl],ELCl]]:
|
def get_pretransformers(self) -> list[Callable[[ELCl],ELCl]]:
|
||||||
return super().get_pretransformers()+[self.handle_if_attribute, self.handle_ifnot_attribute]
|
return super().get_pretransformers()+[self.handle_if_attribute, self.handle_ifnot_attribute]
|
||||||
|
|
||||||
|
@ -66,22 +56,15 @@ class TransformProcessor(NOPProcessor):
|
||||||
return nullify(e)
|
return nullify(e)
|
||||||
return e
|
return e
|
||||||
|
|
||||||
def transform_ListContainer(self, e: ListContainer) -> ListContainer:
|
|
||||||
try:
|
|
||||||
return super().transform_ListContainer(e)
|
|
||||||
except TypeError as err:
|
|
||||||
names = []
|
|
||||||
for el in e:
|
|
||||||
if hasattr(el, "attributes") and "c" in el.attributes:
|
|
||||||
names.append(el.attributes["c"])
|
|
||||||
if len(names) > 0:
|
|
||||||
raise InlineError(f"The command{'s' if len(names) > 1 else ''} {names[0] if len(names) == 1 else names} was called in an Inline way but returned Block content. Put it in a paragraph alone or execute it as a Div using: \n::: {{c={names[0] if len(names) == 1 else '<command_name>'}}}\n:::")
|
|
||||||
else:
|
|
||||||
raise err
|
|
||||||
|
|
||||||
def transform_Doc(self, e: Doc) -> Doc:
|
def transform_Doc(self, e: Doc) -> Doc:
|
||||||
self.init_context(e)
|
if self.context is not None:
|
||||||
|
raise DoubleDocError()
|
||||||
|
self.context = Context(e, self.root_file_path)
|
||||||
|
for module, module_name in self._command_modules:
|
||||||
|
self.context.add_commands_from_module(module, module_name)
|
||||||
e.content = self.transform(e.content)
|
e.content = self.transform(e.content)
|
||||||
|
e.content = [BlockGroup(*e.content, context=self.context)]
|
||||||
return e
|
return e
|
||||||
|
|
||||||
|
|
||||||
|
@ -99,38 +82,22 @@ class TransformProcessor(NOPProcessor):
|
||||||
e.content = self.transform(e.content)
|
e.content = self.transform(e.content)
|
||||||
# OG now has Context so this is not needed per se, but I'm keeping this here for the handling of attribute > context > default value
|
# OG now has Context so this is not needed per se, but I'm keeping this here for the handling of attribute > context > default value
|
||||||
# Pass down "no-srcset" metadatum as attribute down to images.
|
# Pass down "no-srcset" metadatum as attribute down to images.
|
||||||
if "no-srcset" not in e.attributes:
|
if not "no-srcset" in e.attributes:
|
||||||
e.attributes["no-srcset"] = self.context.get_metadata("no-srcset") if self.context.get_metadata("no-srcset") is not None else False
|
e.attributes["no-srcset"] = self.context.get_metadata("no-srcset") if self.context.get_metadata("no-srcset") is not None else False
|
||||||
if "no-img-link" not in e.attributes:
|
|
||||||
e.attributes["no-img-link"] = self.context.get_metadata("no-img-link") if self.context.get_metadata("no-img-link") is not None else False
|
|
||||||
return e
|
return e
|
||||||
|
|
||||||
def create_Group(self, *content, new_context: Context, replaced:Element, inline: bool=False) -> Group:
|
def create_Group(self, *content, new_context: Context, inline: bool=False) -> Group:
|
||||||
old_context = self.context
|
old_context = self.context
|
||||||
self.context = new_context
|
self.context = new_context
|
||||||
if inline:
|
content = self.transform([*content])
|
||||||
g = InlineGroup(*content, context=new_context)
|
|
||||||
else:
|
|
||||||
g = BlockGroup(*content, context=new_context)
|
|
||||||
attach(g, replaced.parent, replaced.location, replaced.index)
|
|
||||||
g = self.transform(g)
|
|
||||||
self.context = old_context
|
self.context = old_context
|
||||||
return g
|
if inline:
|
||||||
|
return InlineGroup(*content, context=new_context)
|
||||||
def transform_Para(self, e: Para) -> Union[Para, Div]:
|
|
||||||
if len(e.content) == 1 and isinstance(e.content[0], Span):
|
|
||||||
# If the span turns out to be a command, it might return a Div. We should then replace ourselves with the Div
|
|
||||||
span = e.content[0]
|
|
||||||
span = self.transform(span)
|
|
||||||
if isinstance(span, Div):
|
|
||||||
return span
|
|
||||||
else:
|
|
||||||
e.content[0] = span
|
|
||||||
return super().transform_Para(e)
|
|
||||||
else:
|
else:
|
||||||
return super().transform_Para(e)
|
return BlockGroup(*content, context=new_context)
|
||||||
|
|
||||||
def transform_Div(self, e: Div) -> Union[Div, Group, Null, RawBlock]:
|
def transform_Div(self, e: Div) -> Union[Div, Group, Null, RawBlock]:
|
||||||
|
e.content = self.transform(e.content)
|
||||||
|
|
||||||
if "group" in e.classes:
|
if "group" in e.classes:
|
||||||
# `.group` class for Divs
|
# `.group` class for Divs
|
||||||
|
@ -138,15 +105,14 @@ class TransformProcessor(NOPProcessor):
|
||||||
new_context = Context(Doc(), self.context.path, self.context, trusted=self.context.trusted)
|
new_context = Context(Doc(), self.context.path, self.context, trusted=self.context.trusted)
|
||||||
for attribute, value in e.attributes.items():
|
for attribute, value in e.attributes.items():
|
||||||
new_context.set_metadata(attribute, value)
|
new_context.set_metadata(attribute, value)
|
||||||
return self.create_Group(*e.content, replaced=e, new_context=new_context)
|
return self.create_Group(*e.content, new_context=new_context)
|
||||||
|
|
||||||
if "c" in e.attributes:
|
if "c" in e.attributes:
|
||||||
# Commands can be called multiple ways, this handles the following syntax:
|
# Commands can be called multiple ways, this handles the following syntax:
|
||||||
# :::{c=commandname}
|
# :::{c=commandname}
|
||||||
# :::
|
# :::
|
||||||
command = BlockCommand(*e.content, identifier=e.identifier, classes=e.classes, attributes=e.attributes)
|
e = BlockCommand(*e.content, identifier=e.identifier, classes=e.classes, attributes=e.attributes)
|
||||||
attach(command, e.parent, e.location, e.index)
|
return self.transform(e)
|
||||||
return self.transform(command)
|
|
||||||
|
|
||||||
if "partial" in e.attributes:
|
if "partial" in e.attributes:
|
||||||
# `partial` attribute
|
# `partial` attribute
|
||||||
|
@ -158,9 +124,7 @@ class TransformProcessor(NOPProcessor):
|
||||||
pwd = os.path.abspath(".")
|
pwd = os.path.abspath(".")
|
||||||
if os.path.commonpath([full_path, pwd]) != os.path.commonpath([pwd]):
|
if os.path.commonpath([full_path, pwd]) != os.path.commonpath([pwd]):
|
||||||
return nullify(e)
|
return nullify(e)
|
||||||
filename = self.context.dir + "/" + e.attributes["partial"]
|
text = open(self.context.dir + "/" + e.attributes["partial"], "r").read()
|
||||||
self.context.add_dep(filename)
|
|
||||||
text = open(filename, "r").read()
|
|
||||||
path = self.context.dir + "/" + e.attributes["partial"]
|
path = self.context.dir + "/" + e.attributes["partial"]
|
||||||
if e.attributes["type"] == "md":
|
if e.attributes["type"] == "md":
|
||||||
includedDoc = import_md(text)
|
includedDoc = import_md(text)
|
||||||
|
@ -169,7 +133,7 @@ class TransformProcessor(NOPProcessor):
|
||||||
trusted = False
|
trusted = False
|
||||||
if not self.context.trusted:
|
if not self.context.trusted:
|
||||||
trusted = False
|
trusted = False
|
||||||
return self.create_Group(*includedDoc.content, replaced=e, new_context=Context(includedDoc, path, self.context, trusted=trusted))
|
return self.create_Group(*includedDoc.content, new_context=Context(includedDoc, path, self.context, trusted=trusted))
|
||||||
elif e.attributes["type"] in ["tex", "html"]:
|
elif e.attributes["type"] in ["tex", "html"]:
|
||||||
return RawBlock(text, e.attributes["type"])
|
return RawBlock(text, e.attributes["type"])
|
||||||
|
|
||||||
|
@ -190,9 +154,10 @@ class TransformProcessor(NOPProcessor):
|
||||||
if "lang" in e.attributes:
|
if "lang" in e.attributes:
|
||||||
warnings.warn("To set language in a way formátítko will understand, this Div has to have the `.group` class and be a Group.", UserWarning)
|
warnings.warn("To set language in a way formátítko will understand, this Div has to have the `.group` class and be a Group.", UserWarning)
|
||||||
|
|
||||||
return super().transform_Div(e)
|
return e
|
||||||
|
|
||||||
def transform_Span(self, e: Span) -> Span:
|
def transform_Span(self, e: Span) -> Span:
|
||||||
|
e.content = self.transform(e.content)
|
||||||
|
|
||||||
if "group" in e.classes:
|
if "group" in e.classes:
|
||||||
# `.group` class for Spans
|
# `.group` class for Spans
|
||||||
|
@ -200,21 +165,19 @@ class TransformProcessor(NOPProcessor):
|
||||||
new_context = Context(Doc(), self.context.path, self.context, trusted=self.context.trusted)
|
new_context = Context(Doc(), self.context.path, self.context, trusted=self.context.trusted)
|
||||||
for attribute, value in e.attributes.items():
|
for attribute, value in e.attributes.items():
|
||||||
new_context.set_metadata(attribute, value)
|
new_context.set_metadata(attribute, value)
|
||||||
return self.create_Group(*e.content, replaced=e, new_context=new_context, inline=True)
|
return self.create_Group(*e.content, new_context=new_context, inline=True)
|
||||||
|
|
||||||
if "c" in e.attributes:
|
if "c" in e.attributes:
|
||||||
# Commands can be called multiple ways, this handles the following syntax:
|
# Commands can be called multiple ways, this handles the following syntax:
|
||||||
# []{c=commandname} and
|
# []{c=commandname} and
|
||||||
command = InlineCommand(*e.content, identifier=e.identifier, classes=e.classes, attributes=e.attributes)
|
e = InlineCommand(*e.content, identifier=e.identifier, classes=e.classes, attributes=e.attributes)
|
||||||
attach(command, e.parent, e.location, e.index)
|
return self.transform(e)
|
||||||
return self.transform(command)
|
|
||||||
|
|
||||||
if len(e.content) == 1 and isinstance(e.content[0], Str):
|
if len(e.content) == 1 and isinstance(e.content[0], Str):
|
||||||
## Handle special command shorthand [!commandname]{}
|
## Handle special command shorthand [!commandname]{}
|
||||||
if re.match(r"^![\w.]+$", e.content[0].text):
|
if re.match(r"^![\w.]+$", e.content[0].text):
|
||||||
command = InlineCommand(identifier=e.identifier, classes=e.classes, attributes={**e.attributes, "c": e.content[0].text[1:]})
|
e = InlineCommand(identifier=e.identifier, classes=e.classes, attributes={**e.attributes, "c": e.content[0].text[1:]})
|
||||||
attach(command, e.parent, e.location, e.index)
|
return self.transform(e)
|
||||||
return self.transform(command)
|
|
||||||
|
|
||||||
## Handle import [#ksp_formatitko as ksp]{}, [#ksp_formatitko]{type=module} or [#path/file.md]{type=md}
|
## Handle import [#ksp_formatitko as ksp]{}, [#ksp_formatitko]{type=module} or [#path/file.md]{type=md}
|
||||||
# Import a python module as commands (type=module, the default) or
|
# Import a python module as commands (type=module, the default) or
|
||||||
|
@ -223,9 +186,7 @@ class TransformProcessor(NOPProcessor):
|
||||||
if not "type" in e.attributes:
|
if not "type" in e.attributes:
|
||||||
e.attributes["type"] = "module"
|
e.attributes["type"] = "module"
|
||||||
if e.attributes["type"] == "md":
|
if e.attributes["type"] == "md":
|
||||||
filename = self.context.dir + "/" + e.content[0].text[1:]
|
importedDoc = import_md(open(self.context.dir + "/" + e.content[0].text[1:], "r").read())
|
||||||
self.context.add_dep(filename)
|
|
||||||
importedDoc = import_md(open(filename, "r").read())
|
|
||||||
self.transform(importedDoc.content)
|
self.transform(importedDoc.content)
|
||||||
elif e.attributes["type"] == "module":
|
elif e.attributes["type"] == "module":
|
||||||
matches = re.match(r"^(\w+)(?: as (\w+))?$", e.content[0].text[1:])
|
matches = re.match(r"^(\w+)(?: as (\w+))?$", e.content[0].text[1:])
|
||||||
|
@ -235,9 +196,7 @@ class TransformProcessor(NOPProcessor):
|
||||||
module_name = matches.group(1) if matches.group(2) is None else matches.group(2)
|
module_name = matches.group(1) if matches.group(2) is None else matches.group(2)
|
||||||
self.context.add_commands_from_module(module, module_name)
|
self.context.add_commands_from_module(module, module_name)
|
||||||
elif e.attributes["type"] == "metadata":
|
elif e.attributes["type"] == "metadata":
|
||||||
filename = self.context.dir + "/" + e.content[0].text[1:]
|
data = json.load(open(self.context.dir + "/" + e.content[0].text[1:], "r"))
|
||||||
self.context.add_dep(filename)
|
|
||||||
data = json.load(open(filename, "r"))
|
|
||||||
key = "" if not "key" in e.attributes else e.attributes["key"]
|
key = "" if not "key" in e.attributes else e.attributes["key"]
|
||||||
self.context.import_metadata(data, key)
|
self.context.import_metadata(data, key)
|
||||||
else:
|
else:
|
||||||
|
@ -249,7 +208,7 @@ class TransformProcessor(NOPProcessor):
|
||||||
# This is a shorthand for just printing the content of some metadata.
|
# This is a shorthand for just printing the content of some metadata.
|
||||||
elif re.match(r"^\$[\w.]+$", e.content[0].text):
|
elif re.match(r"^\$[\w.]+$", e.content[0].text):
|
||||||
val = self.context.get_metadata(e.content[0].text[1:], False)
|
val = self.context.get_metadata(e.content[0].text[1:], False)
|
||||||
if isinstance(val, MetaInlines): # TODO: Trust transform for this
|
if isinstance(val, MetaInlines):
|
||||||
e = Span(*val.content)
|
e = Span(*val.content)
|
||||||
e = self.transform(e)
|
e = self.transform(e)
|
||||||
elif isinstance(val, MetaString):
|
elif isinstance(val, MetaString):
|
||||||
|
@ -260,20 +219,19 @@ class TransformProcessor(NOPProcessor):
|
||||||
raise TypeError(f"Cannot print value of metadatum '{e.content[0].text[1:]}' of type '{type(val)}'")
|
raise TypeError(f"Cannot print value of metadatum '{e.content[0].text[1:]}' of type '{type(val)}'")
|
||||||
return e
|
return e
|
||||||
|
|
||||||
return super().transform_Span(e)
|
return e
|
||||||
|
|
||||||
def transform_CodeBlock(self, e: CodeBlock) -> Union[CodeBlock, Div, Null]:
|
def transform_CodeBlock(self, e: CodeBlock) -> Union[CodeBlock, Div, Null]:
|
||||||
if "markdown" in e.classes and "group" in e.classes:
|
if "markdown" in e.classes and "group" in e.classes:
|
||||||
includedDoc = import_md(e.text)
|
includedDoc = import_md(e.text)
|
||||||
return self.create_Group(*includedDoc.content, replaced=e, new_context=Context(includedDoc, self.context.path, self.context, self.context.trusted))
|
return self.create_Group(*includedDoc.content, new_context=Context(includedDoc, self.context.path, self.context, self.context.trusted))
|
||||||
|
|
||||||
if "python" in e.classes and "run" in e.classes:
|
if "python" in e.classes and "run" in e.classes:
|
||||||
if not self.context.trusted:
|
if not self.context.trusted:
|
||||||
return nullify(e)
|
return nullify(e)
|
||||||
command_output = parse_command(e.text)(BlockCommand(), self.context, self)
|
command_output = parse_command(e.text)(BlockCommand(), self.context)
|
||||||
command = BlockCommand().replaceSelf(*([] if command_output is None else command_output))
|
e = BlockCommand().replaceSelf(*([] if command_output is None else command_output))
|
||||||
attach(command, e.parent, e.location, e.index)
|
return self.transform(e)
|
||||||
return self.transform(command)
|
|
||||||
|
|
||||||
if "python" in e.classes and ("define" in e.attributes or "redefine" in e.attributes):
|
if "python" in e.classes and ("define" in e.attributes or "redefine" in e.attributes):
|
||||||
if not self.context.trusted:
|
if not self.context.trusted:
|
||||||
|
@ -281,9 +239,7 @@ class TransformProcessor(NOPProcessor):
|
||||||
return handle_command_define(e, self.context)
|
return handle_command_define(e, self.context)
|
||||||
|
|
||||||
if "c" in e.attributes:
|
if "c" in e.attributes:
|
||||||
command = CodeCommand(e.text, identifier=e.identifier, classes=e.classes, attributes=e.attributes)
|
return self.transform(CodeCommand(e.text, identifier=e.identifier, classes=e.classes, attributes=e.attributes))
|
||||||
attach(command, e.parent, e.location, e.index)
|
|
||||||
return self.transform(command)
|
|
||||||
|
|
||||||
# Pass down metadata 'highlight' and 'highlight_style' as attribute to CodeBlocks
|
# Pass down metadata 'highlight' and 'highlight_style' as attribute to CodeBlocks
|
||||||
# OG now has Context so this is not needed per se, but I'm keeping this here for the handling of attribute > context > default value
|
# OG now has Context so this is not needed per se, but I'm keeping this here for the handling of attribute > context > default value
|
||||||
|
@ -296,9 +252,9 @@ class TransformProcessor(NOPProcessor):
|
||||||
def transform_Command(self, e: Command) -> Union[Div, Span]:
|
def transform_Command(self, e: Command) -> Union[Div, Span]:
|
||||||
if not self.context.get_command(e.attributes["c"]):
|
if not self.context.get_command(e.attributes["c"]):
|
||||||
raise NameError(f"Command not defined '{e.attributes['c']}'.")
|
raise NameError(f"Command not defined '{e.attributes['c']}'.")
|
||||||
command_output = self.context.get_command(e.attributes["c"])(e, self.context, self)
|
command_output = self.context.get_command(e.attributes["c"])(e, self.context)
|
||||||
e = e.replaceSelf(*([] if command_output is None else command_output))
|
e = e.replaceSelf(*([] if command_output is None else command_output))
|
||||||
return e
|
return self.transform(e)
|
||||||
|
|
||||||
def transform_Whitespace(self, e: Whitespace) -> Whitespace:
|
def transform_Whitespace(self, e: Whitespace) -> Whitespace:
|
||||||
if bavlna(e, self.context):
|
if bavlna(e, self.context):
|
||||||
|
|
|
@ -37,7 +37,7 @@ def parse_string(s: str) -> list[Union[Str, Space]]:
|
||||||
# we ever want to disable or enable some of panflute's markdown extensions,
|
# we ever want to disable or enable some of panflute's markdown extensions,
|
||||||
# this is the place to do it.
|
# this is the place to do it.
|
||||||
def import_md(s: str, standalone: bool=True) -> Union[Doc, list[Element]]:
|
def import_md(s: str, standalone: bool=True) -> Union[Doc, list[Element]]:
|
||||||
return convert_text(s, standalone=standalone, input_format="markdown-definition_lists-latex_macros", extra_args=["--strip-comments"])
|
return convert_text(s, standalone=standalone, input_format="markdown-definition_lists-latex_macros")
|
||||||
|
|
||||||
def import_md_list(s: str) -> list[Element]:
|
def import_md_list(s: str) -> list[Element]:
|
||||||
return import_md(s, standalone=False)
|
return import_md(s, standalone=False)
|
||||||
|
|
|
@ -36,9 +36,9 @@ def bavlna(e: Whitespace, c: Context) -> bool:
|
||||||
if prevC in operators and nextC in numbers:
|
if prevC in operators and nextC in numbers:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# if isinstance(e.prev, Math) or isinstance(e.next, Math):
|
if isinstance(e.prev, Math) or isinstance(e.next, Math):
|
||||||
# # Add no-break spaces around TeX math.
|
# Add no-break spaces around TeX math.
|
||||||
# return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
8
test/test-top.html
Normal file
8
test/test-top.html
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta charset='utf-8'>
|
||||||
|
<link rel='stylesheet' href='https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css' integrity='sha384-vKruj+a13U8yHIkAyGgK1J3ArTLzrFGBbBc0tDp4ad/EyewESeXE/Iv67Aj8gKZ0' crossorigin='anonymous'>
|
||||||
|
</head>
|
||||||
|
<body>
|
26
test/test.md
26
test/test.md
|
@ -3,13 +3,12 @@ title: 'Wooooo a title'
|
||||||
subtitle: 'A subtitle'
|
subtitle: 'A subtitle'
|
||||||
are_we_there_yet: False
|
are_we_there_yet: False
|
||||||
lang: "en"
|
lang: "en"
|
||||||
header-includes: |
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
color: forestgreen;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
---
|
---
|
||||||
|
:::: {.header_content}
|
||||||
|
::: {partial="test-top.html" type="html"}
|
||||||
|
:::
|
||||||
|
::::
|
||||||
|
|
||||||
[#test-files/test-import.md]{type=md}
|
[#test-files/test-import.md]{type=md}
|
||||||
|
|
||||||
[#test.json]{type=metadata key=orgs}
|
[#test.json]{type=metadata key=orgs}
|
||||||
|
@ -199,24 +198,13 @@ ii. wym bro
|
||||||
```python {define=bash}
|
```python {define=bash}
|
||||||
import subprocess
|
import subprocess
|
||||||
c = subprocess.run(["bash", "-c", element.text], stdout=subprocess.PIPE, check=True, encoding="utf-8")
|
c = subprocess.run(["bash", "-c", element.text], stdout=subprocess.PIPE, check=True, encoding="utf-8")
|
||||||
return [pf.CodeBlock(c.stdout)]
|
return [pf.Para(pf.Str(c.stdout))]
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash {c=bash}
|
```bash {c=bash}
|
||||||
cat /etc/os-release
|
cat /etc/hostname
|
||||||
```
|
```
|
||||||
|
|
||||||
::: {.group lang=cs}
|
|
||||||
```python {.run}
|
|
||||||
return processor.transform([
|
|
||||||
*parse_string("V "),
|
|
||||||
pf.Link(pf.Str("odevzdávátku"), url="https://ksp.mff.cuni.cz/z/odevzdavatko/"),
|
|
||||||
*parse_string(" si necháte vygenerovat vstupy a odevzdáte příslušné výstupy. Záleží jen na vás, jak výstupy vyrobíte.")
|
|
||||||
|
|
||||||
])
|
|
||||||
```
|
|
||||||
:::
|
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<div>
|
<div>
|
||||||
hahahahaah
|
hahahahaah
|
||||||
|
|
Loading…
Reference in a new issue