• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1from __future__ import annotations
2import abc
3import typing
4from collections.abc import (
5    Iterable,
6)
7
8import libclinic
9from libclinic import fail
10from libclinic.function import (
11    Module, Class, Function)
12
13if typing.TYPE_CHECKING:
14    from libclinic.app import Clinic
15
16
17class Language(metaclass=abc.ABCMeta):
18
19    start_line = ""
20    body_prefix = ""
21    stop_line = ""
22    checksum_line = ""
23
24    def __init__(self, filename: str) -> None:
25        self.filename = filename
26
27    @abc.abstractmethod
28    def render(
29            self,
30            clinic: Clinic,
31            signatures: Iterable[Module | Class | Function]
32    ) -> str:
33        ...
34
35    def parse_line(self, line: str) -> None:
36        ...
37
38    def validate(self) -> None:
39        def assert_only_one(
40                attr: str,
41                *additional_fields: str
42        ) -> None:
43            """
44            Ensures that the string found at getattr(self, attr)
45            contains exactly one formatter replacement string for
46            each valid field.  The list of valid fields is
47            ['dsl_name'] extended by additional_fields.
48
49            e.g.
50                self.fmt = "{dsl_name} {a} {b}"
51
52                # this passes
53                self.assert_only_one('fmt', 'a', 'b')
54
55                # this fails, the format string has a {b} in it
56                self.assert_only_one('fmt', 'a')
57
58                # this fails, the format string doesn't have a {c} in it
59                self.assert_only_one('fmt', 'a', 'b', 'c')
60
61                # this fails, the format string has two {a}s in it,
62                # it must contain exactly one
63                self.fmt2 = '{dsl_name} {a} {a}'
64                self.assert_only_one('fmt2', 'a')
65
66            """
67            fields = ['dsl_name']
68            fields.extend(additional_fields)
69            line: str = getattr(self, attr)
70            fcf = libclinic.FormatCounterFormatter()
71            fcf.format(line)
72            def local_fail(should_be_there_but_isnt: bool) -> None:
73                if should_be_there_but_isnt:
74                    fail("{} {} must contain {{{}}} exactly once!".format(
75                        self.__class__.__name__, attr, name))
76                else:
77                    fail("{} {} must not contain {{{}}}!".format(
78                        self.__class__.__name__, attr, name))
79
80            for name, count in fcf.counts.items():
81                if name in fields:
82                    if count > 1:
83                        local_fail(True)
84                else:
85                    local_fail(False)
86            for name in fields:
87                if fcf.counts.get(name) != 1:
88                    local_fail(True)
89
90        assert_only_one('start_line')
91        assert_only_one('stop_line')
92
93        field = "arguments" if "{arguments}" in self.checksum_line else "checksum"
94        assert_only_one('checksum_line', field)
95
96
97class PythonLanguage(Language):
98
99    language      = 'Python'
100    start_line    = "#/*[{dsl_name} input]"
101    body_prefix   = "#"
102    stop_line     = "#[{dsl_name} start generated code]*/"
103    checksum_line = "#/*[{dsl_name} end generated code: {arguments}]*/"
104