Browse Source

Trochu zpoužitelněný systém příkazů. Resolves #24.

Asi nemá úplně smysl mít nějaký magický nedeterministický způsob, jak
dělat výstupy příkazů víc inline. Pokud příkazy chtějí být volatelné
inline, musí se postarat o to, že jejich výstup s tím bude kompatibilní.

Příkaz si může ověřit, jestli se očekává inlinovost podle toho, jestli
je parametr `e` třídy BlockCommand nebo `InlineCommand`.
pull/28/head
Jan Černohorský 1 year ago
parent
commit
f63ed7b56a
  1. 13
      src/formatitko/command.py
  2. 1
      src/formatitko/command_env.py
  3. 4
      src/formatitko/transform.py
  4. 31
      src/formatitko/util.py
  5. 3
      test/test-files/test-import.md
  6. 8
      test/test-files/test-partial.md
  7. 3
      test/test.md

13
src/formatitko/command.py

@ -1,7 +1,5 @@
from panflute import Span, Div, Element, Plain, Para from panflute import Span, Div, Element, Plain, Para
from .util import inlinify
class InlineError(Exception): class InlineError(Exception):
pass pass
@ -11,18 +9,21 @@ class Command:
# This distinction is needed because while transforming the tree, inline # This distinction is needed because while transforming the tree, inline
# elements cannot be replaced with block ones # elements cannot be replaced with block ones
class InlineCommand(Span, Command): class InlineCommand(Span, Command):
def replaceSelf(self, content: list[Element]) -> Span: def replaceSelf(self, *content: list[Element]) -> Span:
try: try:
return Span(*content) return Span(*content)
except TypeError: except TypeError:
if inlinify(content): if len(content) == 1 and (isinstance(content[0], Para) or isinstance(content[0], Plain)):
return Span(inlinify(content)) return Span(*content[0].content)
else: else:
raise InlineError(f"The command {self.attributes['c']} returned multiple Paragraphs and must be executed using `::: {{c={self.attributes['c']}}}\\n:::`.\n\n{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):
def replaceSelf(self, content: list[Element]) -> Div: def replaceSelf(self, *content: list[Element]) -> Div:
try:
return Div(*content) return Div(*content)
except TypeError:
return Div(Para(*content))
pass pass

1
src/formatitko/command_env.py

@ -1,6 +1,7 @@
import panflute as pf import panflute as pf
import formatitko.elements as fe import formatitko.elements as fe
from formatitko.util import import_md_list from formatitko.util import import_md_list
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

4
src/formatitko/transform.py

@ -111,7 +111,7 @@ def transform(e: Element, c: Context) -> Element:
if not c.trusted: if not c.trusted:
return nullify(e) return nullify(e)
command_output = parse_command(e.text)(BlockCommand(), c) command_output = parse_command(e.text)(BlockCommand(), c)
e = Div(*([] if command_output is None else command_output)) e = BlockCommand().replaceSelf(*([] if command_output is None else command_output))
e = e.walk(transform, c) e = e.walk(transform, c)
# Command defines for calling using BlockCommand and InlineCommand. If # Command defines for calling using BlockCommand and InlineCommand. If
@ -164,7 +164,7 @@ def transform(e: Element, c: Context) -> Element:
if not c.get_command(e.attributes["c"]): if not c.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 = c.get_command(e.attributes["c"])(e, c) command_output = c.get_command(e.attributes["c"])(e, c)
e = e.replaceSelf([] if command_output is None else command_output) e = e.replaceSelf(*command_output)
e = e.walk(transform, c) e = e.walk(transform, c)
return e return e

31
src/formatitko/util.py

@ -1,22 +1,13 @@
from panflute import Element, Block, Inline, Null, Str, Doc, convert_text, Para, Plain, Span from panflute import Element, Block, Inline, Null, Str, Doc, convert_text, Para, Plain, Span, Space
import re import re
from typing import Union from typing import Union
# It sometimes happens that an element contains a single paragraph or even a # It sometimes happens that an element contains a single paragraph or even a
# single plaintext line. It can be sometimes useful to extract this single # single plaintext line. It can be sometimes useful to extract this single
# paragraph, which is inline. # paragraph, which is inline.
def inlinify(e: Union[Element, list[Element]]) -> Union[Element, None]: def inlinify(e: Element) -> Union[list[Element], None]:
if isinstance(e, Element): if len(e.content) == 1 and (isinstance(e.content[0], Para) or isinstance(e.content[0], Plain)):
content = e.content return e.content[0].content
else:
content = e
if len(content) == 0:
return Str("")
if len(content) == 1 and (isinstance(content[0], Para) or isinstance(content[0], Plain)):
return Span(*content[0].content)
if len(content) == 1 and inlinify(content[0]) is not None:
return inlinify(content[0])
return None
# In transform, inline elements cannot be replaced with Block ones and also # In transform, inline elements cannot be replaced with Block ones and also
# cannot be removed from the tree entirely, because that would mess up the # cannot be removed from the tree entirely, because that would mess up the
@ -28,6 +19,20 @@ def nullify(e: Element) -> Union[Str, Null]:
else: else:
return Null() return Null()
def parse_string(s: str) -> list[Union[Str, Space]]:
words = s.split(" ")
output = []
first_word, *words = words
if first_word != "":
output.append(Str(first_word))
for word in words:
output.append(Space())
if word != "":
output.append(Str(word))
return output
# A helper function to import markdown using panflute (which calls pandoc). If # A helper function to import markdown using panflute (which calls pandoc). If
# 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.

3
test/test-files/test-import.md

@ -5,3 +5,6 @@ return element.content
```python {define=opendatatask} ```python {define=opendatatask}
return import_md_list("Toto je praktická open-data úloha. V [odevzdávátku](https://ksp.mff.cuni.cz/h/odevzdavatko/) si necháte vygenerovat vstupy a odevzdáte příslušné výstupy. Záleží jen na vás, jak výstupy vyrobíte.") return import_md_list("Toto je praktická open-data úloha. V [odevzdávátku](https://ksp.mff.cuni.cz/h/odevzdavatko/) si necháte vygenerovat vstupy a odevzdáte příslušné výstupy. Záleží jen na vás, jak výstupy vyrobíte.")
``` ```
```python {define=opendatatask2}
return [*parse_string("Toto je praktická open-data úloha. V "),pf.Link(pf.Str("odevzdávátku"), url="https://ksp.mff.cuni.cz/h/odevzdavatko/"),*parse_string(" si necháte vygenertovat vstupy a odevzdáte příslušné výstupy. Záleží jen na vás, jak výstupy vyrobíte.")]
```

8
test/test-files/test-partial.md

@ -19,6 +19,14 @@ context.set_flag("cat", True)
```python {.run} ```python {.run}
return import_md_list(f"The subdocument's title is\n\n# {context.get_metadata('title')}\n\nThe subdocument's subtitle is\n\n## {context.get_metadata('subtitle')}") return import_md_list(f"The subdocument's title is\n\n# {context.get_metadata('title')}\n\nThe subdocument's subtitle is\n\n## {context.get_metadata('subtitle')}")
``` ```
```python {.run}
return [
pf.Para(*parse_string("The subdocument's title is")),
pf.Header(*parse_string(context.get_metadata('title')), level=1),
pf.Para(*parse_string("The subdocument's subtitle is")),
pf.Header(*parse_string(context.get_metadata('subtitle')), level=2)
]
```
```markdown {.group} ```markdown {.group}
--- ---

3
test/test.md

@ -38,7 +38,7 @@ context.set_flag("cat", True)
context.set_metadata("a", {}) context.set_metadata("a", {})
context.set_metadata("a.b", {}) context.set_metadata("a.b", {})
context.set_metadata("a.b.c", "Bruh **bruh** bruh") context.set_metadata("a.b.c", "Bruh **bruh** bruh")
return import_md_list(f"The main document's title is '{context.get_metadata('title')}'") return [*parse_string("The main document's title is "), fe.FQuoted(*parse_string(context.get_metadata('title')), style="simple"), pf.Str(".")]
``` ```
```python {style=native} ```python {style=native}
@ -64,6 +64,7 @@ V pravém jízdním bruhu.
V pravém jízdním bruhu. V pravém jízdním bruhu.
[!opendatatask]{} [!opendatatask]{}
[!opendatatask2]{}
``` ```
[This too!]{if=cat} [This too!]{if=cat}

Loading…
Cancel
Save