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