Module ki.transformer

A Lark transformer for the ki note grammar.

Expand source code
#!/usr/bin/env python3
"""A Lark transformer for the ki note grammar."""
import re
from dataclasses import dataclass

from lark import Transformer
from lark.lexer import Token

from beartype import beartype
from beartype.typing import (
    List,
    Dict,
    Optional,
    Union,
)

# pylint: disable=invalid-name, too-few-public-methods

BACKTICKS = "```\n"


@beartype
@dataclass(frozen=True)
class Field:
    """Field content pair."""

    title: str
    content: str


@beartype
@dataclass(frozen=True)
class Header:
    """Note metadata."""

    title: str
    guid: str
    model: str


@beartype
@dataclass(frozen=True)
class FlatNote:
    """Flat (as possible) representation of a note."""

    title: str
    guid: str
    model: str
    tags: List[str]
    fields: Dict[str, str]


class NoteTransformer(Transformer):
    r"""
    note
      header
        title     Note
        guid: 123412341234

        notetype: Basic

        tags      None


      field
        fieldheader
          ###
          Front
        r


      field
        fieldheader
          ###
          Back
        s
    """
    # pylint: disable=missing-function-docstring

    @beartype
    def note(self, n: List[Union[Header, List[str], Field]]) -> FlatNote:
        assert len(n) >= 3

        header = n[0]
        tags = n[1]
        fields = n[2:]
        assert isinstance(header, Header)
        assert isinstance(fields[0], Field)

        # We drop the first character because it is a newline.
        fieldmap: Dict[str, str] = {}
        for field in fields:
            fieldmap[field.title] = field.content[1:]

        return FlatNote(
            title=header.title,
            guid=header.guid,
            model=header.model,
            tags=tags,
            fields=fieldmap,
        )

    @beartype
    def header(self, h: List[str]) -> Header:
        h = filter(lambda s: s != BACKTICKS, h)
        return Header(*h)

    @beartype
    def title(self, t: List[str]) -> str:
        """``title: "##" TITLENAME "\n"+``"""
        assert len(t) == 1
        return t[0]

    @beartype
    def tags(self, tags: List[Optional[str]]) -> List[str]:
        tags = filter(lambda t: t != BACKTICKS, tags)
        return [tag for tag in tags if tag is not None]

    @beartype
    def field(self, f: List[str]) -> Field:
        assert len(f) >= 1
        fheader = f[0]
        lines = f[1:]
        content = "".join(lines)
        if content[-2:] != "\n\n":
            raise RuntimeError(
                f"Nonterminating fields must have >= 1 trailing empty line:\n{content}"
            )
        return Field(fheader, content[:-1])

    @beartype
    def lastfield(self, f: List[str]) -> Field:
        assert len(f) >= 1
        fheader = f[0]
        lines = f[1:]
        content = "".join(lines)
        if len(content) > 0 and content[-1] == "\n":
            content = content[:-1]
        return Field(fheader, content)

    @beartype
    def fieldheader(self, f: List[str]) -> str:
        """``fieldheader: "##" " "* ANKINAME "\n"+``"""
        assert len(f) == 1
        return f[0]

    @beartype
    def GUID(self, t: Token) -> str:
        """Possibly empty for new markdown notes."""
        return re.sub(r"^guid:", "", str(t)).strip()

    @beartype
    def NOTETYPE(self, t: Token) -> str:
        model = re.sub(r"^notetype:", "", str(t)).strip()
        return model

    @beartype
    def FIELDLINE(self, t: Token) -> str:
        return str(t)

    @beartype
    def TITLENAME(self, t: Token) -> str:
        return str(t)

    @beartype
    def ANKINAME(self, t: Token) -> str:
        return str(t)

    @beartype
    def TAGNAME(self, t: Token) -> str:
        return str(t)

    @beartype
    def TRIPLEBACKTICKS(self, t: Token) -> str:
        return str(t)

Classes

class Field (title: str, content: str)

Field content pair.

Expand source code
@beartype
@dataclass(frozen=True)
class Field:
    """Field content pair."""

    title: str
    content: str

Class variables

var content : str
var title : str
class FlatNote (title: str, guid: str, model: str, tags: list[str], fields: dict[str, str])

Flat (as possible) representation of a note.

Expand source code
@beartype
@dataclass(frozen=True)
class FlatNote:
    """Flat (as possible) representation of a note."""

    title: str
    guid: str
    model: str
    tags: List[str]
    fields: Dict[str, str]

Class variables

var fields : dict[str, str]
var guid : str
var model : str
var tags : list[str]
var title : str
class Header (title: str, guid: str, model: str)

Note metadata.

Expand source code
@beartype
@dataclass(frozen=True)
class Header:
    """Note metadata."""

    title: str
    guid: str
    model: str

Class variables

var guid : str
var model : str
var title : str
class NoteTransformer (visit_tokens: bool = True)

note header title Note guid: 123412341234

notetype: Basic

tags      None

field fieldheader ### Front r

field fieldheader ### Back s

Expand source code
class NoteTransformer(Transformer):
    r"""
    note
      header
        title     Note
        guid: 123412341234

        notetype: Basic

        tags      None


      field
        fieldheader
          ###
          Front
        r


      field
        fieldheader
          ###
          Back
        s
    """
    # pylint: disable=missing-function-docstring

    @beartype
    def note(self, n: List[Union[Header, List[str], Field]]) -> FlatNote:
        assert len(n) >= 3

        header = n[0]
        tags = n[1]
        fields = n[2:]
        assert isinstance(header, Header)
        assert isinstance(fields[0], Field)

        # We drop the first character because it is a newline.
        fieldmap: Dict[str, str] = {}
        for field in fields:
            fieldmap[field.title] = field.content[1:]

        return FlatNote(
            title=header.title,
            guid=header.guid,
            model=header.model,
            tags=tags,
            fields=fieldmap,
        )

    @beartype
    def header(self, h: List[str]) -> Header:
        h = filter(lambda s: s != BACKTICKS, h)
        return Header(*h)

    @beartype
    def title(self, t: List[str]) -> str:
        """``title: "##" TITLENAME "\n"+``"""
        assert len(t) == 1
        return t[0]

    @beartype
    def tags(self, tags: List[Optional[str]]) -> List[str]:
        tags = filter(lambda t: t != BACKTICKS, tags)
        return [tag for tag in tags if tag is not None]

    @beartype
    def field(self, f: List[str]) -> Field:
        assert len(f) >= 1
        fheader = f[0]
        lines = f[1:]
        content = "".join(lines)
        if content[-2:] != "\n\n":
            raise RuntimeError(
                f"Nonterminating fields must have >= 1 trailing empty line:\n{content}"
            )
        return Field(fheader, content[:-1])

    @beartype
    def lastfield(self, f: List[str]) -> Field:
        assert len(f) >= 1
        fheader = f[0]
        lines = f[1:]
        content = "".join(lines)
        if len(content) > 0 and content[-1] == "\n":
            content = content[:-1]
        return Field(fheader, content)

    @beartype
    def fieldheader(self, f: List[str]) -> str:
        """``fieldheader: "##" " "* ANKINAME "\n"+``"""
        assert len(f) == 1
        return f[0]

    @beartype
    def GUID(self, t: Token) -> str:
        """Possibly empty for new markdown notes."""
        return re.sub(r"^guid:", "", str(t)).strip()

    @beartype
    def NOTETYPE(self, t: Token) -> str:
        model = re.sub(r"^notetype:", "", str(t)).strip()
        return model

    @beartype
    def FIELDLINE(self, t: Token) -> str:
        return str(t)

    @beartype
    def TITLENAME(self, t: Token) -> str:
        return str(t)

    @beartype
    def ANKINAME(self, t: Token) -> str:
        return str(t)

    @beartype
    def TAGNAME(self, t: Token) -> str:
        return str(t)

    @beartype
    def TRIPLEBACKTICKS(self, t: Token) -> str:
        return str(t)

Ancestors

  • lark.visitors.Transformer
  • lark.visitors._Decoratable
  • abc.ABC
  • typing.Generic

Methods

def ANKINAME(self, t: lark.lexer.Token) ‑> str
Expand source code
@beartype
def ANKINAME(self, t: Token) -> str:
    return str(t)
def FIELDLINE(self, t: lark.lexer.Token) ‑> str
Expand source code
@beartype
def FIELDLINE(self, t: Token) -> str:
    return str(t)
def GUID(self, t: lark.lexer.Token) ‑> str

Possibly empty for new markdown notes.

Expand source code
@beartype
def GUID(self, t: Token) -> str:
    """Possibly empty for new markdown notes."""
    return re.sub(r"^guid:", "", str(t)).strip()
def NOTETYPE(self, t: lark.lexer.Token) ‑> str
Expand source code
@beartype
def NOTETYPE(self, t: Token) -> str:
    model = re.sub(r"^notetype:", "", str(t)).strip()
    return model
def TAGNAME(self, t: lark.lexer.Token) ‑> str
Expand source code
@beartype
def TAGNAME(self, t: Token) -> str:
    return str(t)
def TITLENAME(self, t: lark.lexer.Token) ‑> str
Expand source code
@beartype
def TITLENAME(self, t: Token) -> str:
    return str(t)
def TRIPLEBACKTICKS(self, t: lark.lexer.Token) ‑> str
Expand source code
@beartype
def TRIPLEBACKTICKS(self, t: Token) -> str:
    return str(t)
def field(self, f: list[str]) ‑> Field
Expand source code
@beartype
def field(self, f: List[str]) -> Field:
    assert len(f) >= 1
    fheader = f[0]
    lines = f[1:]
    content = "".join(lines)
    if content[-2:] != "\n\n":
        raise RuntimeError(
            f"Nonterminating fields must have >= 1 trailing empty line:\n{content}"
        )
    return Field(fheader, content[:-1])
def fieldheader(self, f: list[str]) ‑> str

fieldheader: "##" " "* ANKINAME " "+

Expand source code
@beartype
def fieldheader(self, f: List[str]) -> str:
    """``fieldheader: "##" " "* ANKINAME "\n"+``"""
    assert len(f) == 1
    return f[0]
def header(self, h: list[str]) ‑> Header
Expand source code
@beartype
def header(self, h: List[str]) -> Header:
    h = filter(lambda s: s != BACKTICKS, h)
    return Header(*h)
def lastfield(self, f: list[str]) ‑> Field
Expand source code
@beartype
def lastfield(self, f: List[str]) -> Field:
    assert len(f) >= 1
    fheader = f[0]
    lines = f[1:]
    content = "".join(lines)
    if len(content) > 0 and content[-1] == "\n":
        content = content[:-1]
    return Field(fheader, content)
def note(self, n: list[typing.Union[Header, list[str], Field]]) ‑> FlatNote
Expand source code
@beartype
def note(self, n: List[Union[Header, List[str], Field]]) -> FlatNote:
    assert len(n) >= 3

    header = n[0]
    tags = n[1]
    fields = n[2:]
    assert isinstance(header, Header)
    assert isinstance(fields[0], Field)

    # We drop the first character because it is a newline.
    fieldmap: Dict[str, str] = {}
    for field in fields:
        fieldmap[field.title] = field.content[1:]

    return FlatNote(
        title=header.title,
        guid=header.guid,
        model=header.model,
        tags=tags,
        fields=fieldmap,
    )
def tags(self, tags: list[typing.Optional[str]]) ‑> list[str]
Expand source code
@beartype
def tags(self, tags: List[Optional[str]]) -> List[str]:
    tags = filter(lambda t: t != BACKTICKS, tags)
    return [tag for tag in tags if tag is not None]
def title(self, t: list[str]) ‑> str

title: "##" TITLENAME " "+

Expand source code
@beartype
def title(self, t: List[str]) -> str:
    """``title: "##" TITLENAME "\n"+``"""
    assert len(t) == 1
    return t[0]