summaryrefslogtreecommitdiff
path: root/lib/sly/docparse.py
blob: 6a60eaf7ed724f2ba1656a24a0f83dc8eb76ffeb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# docparse.py
#
# Support doc-string parsing classes

__all__ = ["DocParseMeta"]


class DocParseMeta(type):
    '''
    Metaclass that processes the class docstring through a parser and
    incorporates the result into the resulting class definition. This
    allows Python classes to be defined with alternative syntax.
    To use this class, you first need to define a lexer and parser:

        from sly import Lexer, Parser
        class MyLexer(Lexer):
           ...

        class MyParser(Parser):
           ...

    You then need to define a metaclass that inherits from DocParseMeta.
    This class must specify the associated lexer and parser classes.
    For example:

        class MyDocParseMeta(DocParseMeta):
            lexer = MyLexer
            parser = MyParser

    This metaclass is then used as a base for processing user-defined
    classes:

        class Base(metaclass=MyDocParseMeta):
            pass

        class Spam(Base):
            """
            doc string is parsed
            ...
            """

    It is expected that the MyParser() class would return a dictionary.
    This dictionary is used to create the final class Spam in this example.
    '''

    @staticmethod
    def __new__(meta, clsname, bases, clsdict):
        if "__doc__" in clsdict:
            lexer = meta.lexer()
            parser = meta.parser()
            lexer.cls_name = parser.cls_name = clsname
            lexer.cls_qualname = parser.cls_qualname = clsdict["__qualname__"]
            lexer.cls_module = parser.cls_module = clsdict["__module__"]
            parsedict = parser.parse(lexer.tokenize(clsdict["__doc__"]))
            assert isinstance(parsedict, dict), "Parser must return a dictionary"
            clsdict.update(parsedict)
        return super().__new__(meta, clsname, bases, clsdict)

    @classmethod
    def __init_subclass__(cls):
        assert hasattr(cls, "parser") and hasattr(cls, "lexer")