File indexing completed on 2024-04-21 11:25:48

0001 #!/usr/bin/env python
0002 #
0003 # SPDX-FileCopyrightText: 2016 by Shaheed Haque <srhaque@theiet.org>
0004 # SPDX-FileCopyrightText: 2016 Stephen Kelly <steveire@gmail.com>
0005 #
0006 # SPDX-License-Identifier: BSD-3-Clause
0007 
0008 """SIP file generation rules engine."""
0009 
0010 from __future__ import print_function
0011 
0012 from abc import *
0013 import argparse
0014 import gettext
0015 import inspect
0016 import logging
0017 import os
0018 import re
0019 import sys
0020 import textwrap
0021 from copy import deepcopy
0022 from clang.cindex import CursorKind
0023 
0024 from clang.cindex import AccessSpecifier
0025 
0026 class HelpFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter):
0027     pass
0028 
0029 
0030 logger = logging.getLogger(__name__)
0031 gettext.install(__name__)
0032 _SEPARATOR = "\x00"
0033 
0034 def _parents(container):
0035     parents = []
0036     parent = container.semantic_parent
0037     while parent and parent.kind != CursorKind.TRANSLATION_UNIT:
0038         parents.append(parent.spelling)
0039         parent = parent.semantic_parent
0040     if parents:
0041         parents = "::".join(reversed(parents))
0042     else:
0043         parents = os.path.basename(container.translation_unit.spelling)
0044     return parents
0045 
0046 
0047 class Rule(object):
0048     def __init__(self, db, rule_number, fn, pattern_zip):
0049         #
0050         # Derive a useful name for diagnostic purposes.
0051         #
0052         caller = os.path.basename(inspect.stack()[3][1])
0053         self.name = "{}:{}[{}],{}".format(caller, type(db).__name__, rule_number, fn.__name__)
0054         self.rule_number = rule_number
0055         self.fn = fn
0056         self.usage = 0
0057         try:
0058             groups = ["(?P<{}>{})".format(name, pattern) for pattern, name in pattern_zip]
0059             groups = _SEPARATOR.join(groups)
0060             #
0061             # We'll use re.match to anchor the start of the match, and so need a $ to anchor the end.
0062             #
0063             self.matcher = re.compile(groups + "$")
0064         except Exception as e:
0065             groups = ["{} '{}'".format(name, pattern) for pattern, name in pattern_zip]
0066             groups = ", ".join(groups)
0067             raise RuntimeError(_("Bad {}: {}: {}").format(self, groups, e))
0068 
0069     def match(self, candidate):
0070         return self.matcher.match(candidate)
0071 
0072     def trace_result(self, parents, item, original, modified):
0073         """
0074         Record any modification both in the log and the returned result. If a rule fired, but
0075         caused no modification, that is logged.
0076 
0077         :return: Modifying rule or None.
0078         """
0079         fqn = parents + "::" + original["name"] + "[" + str(item.extent.start.line) + "]"
0080         return self._trace_result(fqn, original, modified)
0081 
0082     def _trace_result(self, fqn, original, modified):
0083         """
0084         Record any modification both in the log and the returned result. If a rule fired, but
0085         caused no modification, that is logged.
0086 
0087         :return: Modifying rule or None.
0088         """
0089         if not modified["name"]:
0090             logger.debug(_("Rule {} suppressed {}, {}").format(self, fqn, original))
0091         else:
0092             delta = False
0093             for k, v in original.items():
0094                 if v != modified[k]:
0095                     delta = True
0096                     break
0097             if delta:
0098                 logger.debug(_("Rule {} modified {}, {}->{}").format(self, fqn, original, modified))
0099             else:
0100                 logger.warn(_("Rule {} did not modify {}, {}").format(self, fqn, original))
0101                 return None
0102         return self
0103 
0104     def __str__(self):
0105         return self.name
0106 
0107 
0108 class AbstractCompiledRuleDb(object):
0109     __metaclass__ = ABCMeta
0110 
0111     def __init__(self, db, parameter_names):
0112         self.db = db
0113         self.compiled_rules = []
0114         for i, raw_rule in enumerate(db()):
0115             if len(raw_rule) != len(parameter_names) + 1:
0116                 raise RuntimeError(_("Bad raw rule {}: {}: {}").format(db.__name__, raw_rule, parameter_names))
0117             z = zip(raw_rule[:-1], parameter_names)
0118             self.compiled_rules.append(Rule(self, i, raw_rule[-1], z))
0119         self.candidate_formatter = _SEPARATOR.join(["{}"] * len(parameter_names))
0120 
0121     def _match(self, *args):
0122         candidate = self.candidate_formatter.format(*args)
0123         for rule in self.compiled_rules:
0124             matcher = rule.match(candidate)
0125             if matcher:
0126                 #
0127                 # Only use the first matching rule.
0128                 #
0129                 rule.usage += 1
0130                 return matcher, rule
0131         return None, None
0132 
0133     @abstractmethod
0134     def apply(self, *args):
0135         raise NotImplemented(_("Missing subclass"))
0136 
0137     def dump_usage(self, fn):
0138         """ Dump the usage counts."""
0139         for rule in self.compiled_rules:
0140             fn(str(rule), rule.usage)
0141 
0142 
0143 class ContainerRuleDb(AbstractCompiledRuleDb):
0144     """
0145     THE RULES FOR CONTAINERS.
0146 
0147     These are used to customize the behaviour of the SIP generator by allowing
0148     the declaration for any container (class, namespace, struct, union) to be
0149     customized, for example to add SIP compiler annotations.
0150 
0151     Each entry in the raw rule database must be a list with members as follows:
0152 
0153         0. A regular expression which matches the fully-qualified name of the
0154         "container" enclosing the container.
0155 
0156         1. A regular expression which matches the container name.
0157 
0158         2. A regular expression which matches any template parameters.
0159 
0160         3. A regular expression which matches the container declaration.
0161 
0162         4. A regular expression which matches any base specifiers.
0163 
0164         5. A function.
0165 
0166     In use, the database is walked in order from the first entry. If the regular
0167     expressions are matched, the function is called, and no further entries are
0168     walked. The function is called with the following contract:
0169 
0170         def container_xxx(container, sip, matcher):
0171             '''
0172             Return a modified declaration for the given container.
0173 
0174             :param container:   The clang.cindex.Cursor for the container.
0175             :param sip:         A dict with the following keys:
0176 
0177                                     name                The name of the container.
0178                                     template_parameters Any template parameters.
0179                                     decl                The declaration.
0180                                     base_specifiers     Any base specifiers.
0181                                     body                The body, less the outer
0182                                                         pair of braces.
0183                                     annotations         Any SIP annotations.
0184 
0185             :param matcher:         The re.Match object. This contains named
0186                                     groups corresponding to the key names above
0187                                     EXCEPT body and annotations.
0188 
0189             :return: An updated set of sip.xxx values. Setting sip.name to the
0190                      empty string will cause the container to be suppressed.
0191             '''
0192 
0193     :return: The compiled form of the rules.
0194     """
0195     def __init__(self, db):
0196         super(ContainerRuleDb, self).__init__(db, ["parents", "container", "template_parameters", "decl", "base_specifiers"])
0197 
0198     def apply(self, container, sip):
0199         """
0200         Walk over the rules database for containers, applying the first matching transformation.
0201 
0202         :param container:           The clang.cindex.Cursor for the container.
0203         :param sip:                 The SIP dict (may be modified on return).
0204         :return:                    Modifying rule or None (even if a rule matched, it may not modify things).
0205         """
0206         parents = _parents(container)
0207         matcher, rule = self._match(parents, sip["name"],
0208                                     ", ".join(sip["template_parameters"]),
0209                                     sip["decl"],
0210                                     ", ".join(sip["base_specifiers"]))
0211         if matcher:
0212             before = deepcopy(sip)
0213             rule.fn(container, sip, matcher)
0214             return rule.trace_result(parents, container, before, sip)
0215         return None
0216 
0217 
0218 class ForwardDeclarationRuleDb(AbstractCompiledRuleDb):
0219     """
0220     THE RULES FOR FORWARD DECLARATIONS.
0221 
0222     These are used to customize the behaviour of the SIP generator by allowing
0223     the forward declaration for any container (class, struct, union) to be
0224     customized, for example to add SIP compiler annotations.
0225 
0226     Each entry in the raw rule database must be a list with members as follows:
0227 
0228         0. A regular expression which matches the fully-qualified name of the
0229         "container" enclosing the container.
0230 
0231         1. A regular expression which matches the container name.
0232 
0233         2. A regular expression which matches any template parameters.
0234 
0235         3. A function.
0236 
0237     In use, the database is walked in order from the first entry. If the regular
0238     expressions are matched, the function is called, and no further entries are
0239     walked. The function is called with the following contract:
0240 
0241         def declaration_xxx(container, sip, matcher):
0242             '''
0243             Return a modified declaration for the given container.
0244 
0245             :param container:   The clang.cindex.Cursor for the container.
0246             :param sip:         A dict with the following keys:
0247 
0248                                     name                The name of the container.
0249                                     template_parameters Any template parameters.
0250                                     annotations         Any SIP annotations.
0251 
0252             :param matcher:         The re.Match object. This contains named
0253                                     groups corresponding to the key names above
0254                                     EXCEPT body and annotations.
0255 
0256             :return: An updated set of sip.xxx values. Setting sip.name to the
0257                      empty string will cause the container to be suppressed.
0258             '''
0259 
0260     :return: The compiled form of the rules.
0261     """
0262     def __init__(self, db):
0263         super(ForwardDeclarationRuleDb, self).__init__(db, ["parents", "container", "template_parameters"])
0264 
0265     def apply(self, container, sip):
0266         """
0267         Walk over the rules database for containers, applying the first matching transformation.
0268 
0269         :param container:           The clang.cindex.Cursor for the container.
0270         :param sip:                 The SIP dict (may be modified on return).
0271         :return:                    Modifying rule or None (even if a rule matched, it may not modify things).
0272         """
0273         parents = _parents(container)
0274         matcher, rule = self._match(parents, sip["name"],
0275                                     ", ".join(sip["template_parameters"]))
0276         if matcher:
0277             before = deepcopy(sip)
0278             rule.fn(container, sip, matcher)
0279             return rule.trace_result(parents, container, before, sip)
0280         return None
0281 
0282 
0283 class FunctionRuleDb(AbstractCompiledRuleDb):
0284     """
0285     THE RULES FOR FUNCTIONS.
0286 
0287     These are used to customize the behaviour of the SIP generator by allowing
0288     the declaration for any function to be customized, for example to add SIP
0289     compiler annotations.
0290 
0291     Each entry in the raw rule database must be a list with members as follows:
0292 
0293         0. A regular expression which matches the fully-qualified name of the
0294         "container" enclosing the function.
0295 
0296         1. A regular expression which matches the function name.
0297 
0298         2. A regular expression which matches any template parameters.
0299 
0300         3. A regular expression which matches the function result.
0301 
0302         4. A regular expression which matches the function parameters (e.g.
0303         "int a, void *b" for "int foo(int a, void *b)").
0304 
0305         5. A function.
0306 
0307     In use, the database is walked in order from the first entry. If the regular
0308     expressions are matched, the function is called, and no further entries are
0309     walked. The function is called with the following contract:
0310 
0311         def function_xxx(container, function, sip, matcher):
0312             '''
0313             Return a modified declaration for the given function.
0314 
0315             :param container:   The clang.cindex.Cursor for the container.
0316             :param function:    The clang.cindex.Cursor for the function.
0317             :param sip:         A dict with the following keys:
0318 
0319                                     name                The name of the function.
0320                                     template_parameters Any template parameters.
0321                                     fn_result           Result, if not a constructor.
0322                                     parameters          The parameters.
0323                                     prefix              Leading keyworks ("static"). Separated by space,
0324                                                         ends with a space.
0325                                     suffix              Trailing keywords ("const"). Separated by space, starts with
0326                                                         space.
0327                                     annotations         Any SIP annotations.
0328 
0329             :param matcher:         The re.Match object. This contains named
0330                                     groups corresponding to the key names above
0331                                     EXCEPT annotations.
0332 
0333             :return: An updated set of sip.xxx values. Setting sip.name to the
0334                      empty string will cause the container to be suppressed.
0335             '''
0336 
0337     :return: The compiled form of the rules.
0338     """
0339     def __init__(self, db):
0340         super(FunctionRuleDb, self).__init__(db, ["container", "function", "template_parameters", "fn_result", "parameters"])
0341 
0342     def apply(self, container, function, sip):
0343         """
0344         Walk over the rules database for functions, applying the first matching transformation.
0345 
0346         :param container:           The clang.cindex.Cursor for the container.
0347         :param function:            The clang.cindex.Cursor for the function.
0348         :param sip:                 The SIP dict (may be modified on return).
0349         :return:                    Modifying rule or None (even if a rule matched, it may not modify things).
0350         """
0351         parents = _parents(function)
0352         matcher, rule = self._match(parents, sip["name"], ", ".join(sip["template_parameters"]), sip["fn_result"], ", ".join(sip["parameters"]))
0353         if matcher:
0354             sip.setdefault("code", "")
0355             before = deepcopy(sip)
0356             rule.fn(container, function, sip, matcher)
0357             return rule.trace_result(parents, function, before, sip)
0358         return None
0359 
0360 
0361 class ParameterRuleDb(AbstractCompiledRuleDb):
0362     """
0363     THE RULES FOR FUNCTION PARAMETERS.
0364 
0365     These are used to customize the behaviour of the SIP generator by allowing
0366     the declaration for any parameter in any function to be customized, for
0367     example to add SIP compiler annotations.
0368 
0369     Each entry in the raw rule database must be a list with members as follows:
0370 
0371         0. A regular expression which matches the fully-qualified name of the
0372         "container" enclosing the function enclosing the parameter.
0373 
0374         1. A regular expression which matches the function name enclosing the
0375         parameter.
0376 
0377         2. A regular expression which matches the parameter name.
0378 
0379         3. A regular expression which matches the parameter declaration (e.g.
0380         "int foo").
0381 
0382         4. A regular expression which matches the parameter initialiser (e.g.
0383         "Xyz:MYCONST + 42").
0384 
0385         5. A function.
0386 
0387     In use, the database is walked in order from the first entry. If the regular
0388     expressions are matched, the function is called, and no further entries are
0389     walked. The function is called with the following contract:
0390 
0391         def parameter_xxx(container, function, parameter, sip, init, matcher):
0392             '''
0393             Return a modified declaration and initialiser for the given parameter.
0394 
0395             :param container:   The clang.cindex.Cursor for the container.
0396             :param function:    The clang.cindex.Cursor for the function.
0397             :param parameter:   The clang.cindex.Cursor for the parameter.
0398             :param sip:         A dict with the following keys:
0399 
0400                                     name                The name of the function.
0401                                     decl                The declaration.
0402                                     init                Any initialiser.
0403                                     annotations         Any SIP annotations.
0404 
0405             :param matcher:         The re.Match object. This contains named
0406                                     groups corresponding to the key names above
0407                                     EXCEPT annotations.
0408 
0409             :return: An updated set of sip.xxx values.
0410         '''
0411 
0412     :return: The compiled form of the rules.
0413     """
0414     def __init__(self, db):
0415         super(ParameterRuleDb, self).__init__(db, ["container", "function", "parameter", "decl", "init"])
0416 
0417     def apply(self, container, function, parameter, sip):
0418         """
0419         Walk over the rules database for parameters, applying the first matching transformation.
0420 
0421         :param container:           The clang.cindex.Cursor for the container.
0422         :param function:            The clang.cindex.Cursor for the function.
0423         :param parameter:           The clang.cindex.Cursor for the parameter.
0424         :param sip:                 The SIP dict (may be modified on return).
0425         :return:                    Modifying rule or None (even if a rule matched, it may not modify things).
0426         """
0427         parents = _parents(function)
0428         matcher, rule = self._match(parents, function.spelling, sip["name"], sip["decl"], sip["init"])
0429         if matcher:
0430             sip.setdefault("code", "")
0431             before = deepcopy(sip)
0432             rule.fn(container, function, parameter, sip, matcher)
0433             return rule.trace_result(parents, parameter, before, sip)
0434         return None
0435 
0436 
0437 class TypedefRuleDb(AbstractCompiledRuleDb):
0438     """
0439     THE RULES FOR TYPEDEFS.
0440 
0441     These are used to customize the behaviour of the SIP generator by allowing
0442     the declaration for any typedef to be customized, for example to add SIP
0443     compiler annotations.
0444 
0445     Each entry in the raw rule database must be a list with members as follows:
0446 
0447         0. A regular expression which matches the fully-qualified name of the
0448         "container" enclosing the typedef.
0449 
0450         1. A regular expression which matches the typedef name.
0451 
0452         2. A function.
0453 
0454     In use, the database is walked in order from the first entry. If the regular
0455     expressions are matched, the function is called, and no further entries are
0456     walked. The function is called with the following contract:
0457 
0458         def typedef_xxx(container, typedef, sip, matcher):
0459             '''
0460             Return a modified declaration for the given function.
0461 
0462             :param container:   The clang.cindex.Cursor for the container.
0463             :param typedef:     The clang.cindex.Cursor for the typedef.
0464             :param sip:         A dict with the following keys:
0465 
0466                                     name                The name of the typedef.
0467                                     annotations         Any SIP annotations.
0468 
0469             :param matcher:         The re.Match object. This contains named
0470                                     groups corresponding to the key names above
0471                                     EXCEPT annotations.
0472 
0473             :return: An updated set of sip.xxx values. Setting sip.name to the
0474                      empty string will cause the container to be suppressed.
0475             '''
0476 
0477     :return: The compiled form of the rules.
0478     """
0479     def __init__(self, db):
0480         super(TypedefRuleDb, self).__init__(db, ["container", "typedef"])
0481 
0482     def apply(self, container, typedef, sip):
0483         """
0484         Walk over the rules database for typedefs, applying the first matching transformation.
0485 
0486         :param container:           The clang.cindex.Cursor for the container.
0487         :param typedef:             The clang.cindex.Cursor for the typedef.
0488         :param sip:                 The SIP dict.
0489         """
0490         parents = _parents(typedef)
0491         matcher, rule = self._match(parents, sip["name"])
0492         if matcher:
0493             before = deepcopy(sip)
0494             rule.fn(container, typedef, sip, matcher)
0495             return rule.trace_result(parents, typedef, before, sip)
0496         return None
0497 
0498 
0499 class VariableRuleDb(AbstractCompiledRuleDb):
0500     """
0501     THE RULES FOR VARIABLES.
0502 
0503     These are used to customize the behaviour of the SIP generator by allowing
0504     the declaration for any variable to be customized, for example to add SIP
0505     compiler annotations.
0506 
0507     Each entry in the raw rule database must be a list with members as follows:
0508 
0509         0. A regular expression which matches the fully-qualified name of the
0510         "container" enclosing the variable.
0511 
0512         1. A regular expression which matches the variable name.
0513 
0514         2. A regular expression which matches the variable declaration (e.g.
0515         "int foo").
0516 
0517         3. A function.
0518 
0519     In use, the database is walked in order from the first entry. If the regular
0520     expressions are matched, the function is called, and no further entries are
0521     walked. The function is called with the following contract:
0522 
0523         def variable_xxx(container, variable, sip, matcher):
0524             '''
0525             Return a modified declaration for the given variable.
0526 
0527             :param container:   The clang.cindex.Cursor for the container.
0528             :param variable:    The clang.cindex.Cursor for the variable.
0529             :param sip:         A dict with the following keys:
0530 
0531                                     name                The name of the variable.
0532                                     decl                The declaration.
0533                                     annotations         Any SIP annotations.
0534 
0535             :param matcher:         The re.Match object. This contains named
0536                                     groups corresponding to the key names above
0537                                     EXCEPT annotations.
0538 
0539             :return: An updated set of sip.xxx values. Setting sip.name to the
0540                      empty string will cause the container to be suppressed.
0541             '''
0542 
0543     :return: The compiled form of the rules.
0544     """
0545     def __init__(self, db):
0546         super(VariableRuleDb, self).__init__(db, ["container", "variable", "decl"])
0547 
0548     def apply(self, container, variable, sip):
0549         """
0550         Walk over the rules database for variables, applying the first matching transformation.
0551 
0552         :param container:           The clang.cindex.Cursor for the container.
0553         :param variable:            The clang.cindex.Cursor for the variable.
0554         :param sip:                 The SIP dict (may be modified on return).
0555         :return:                    Modifying rule or None (even if a rule matched, it may not modify things).
0556         """
0557         parents = _parents(variable)
0558         matcher, rule = self._match(parents, sip["name"], sip["decl"])
0559         if matcher:
0560             sip.setdefault("code", "")
0561             before = deepcopy(sip)
0562             rule.fn(container, variable, sip, matcher)
0563             return rule.trace_result(parents, variable, before, sip)
0564         return None
0565 
0566 
0567 class AbstractCompiledCodeDb(object):
0568     __metaclass__ = ABCMeta
0569 
0570     def __init__(self, db):
0571         caller = os.path.basename(inspect.stack()[2][1])
0572         self.name = "{}:{}".format(caller, type(self).__name__)
0573         self.db = db
0574 
0575     @abstractmethod
0576     def apply(self, function, sip):
0577         raise NotImplemented(_("Missing subclass"))
0578 
0579     def trace_result(self, parents, item, original, modified):
0580         """
0581         Record any modification both in the log and the returned result. If a rule fired, but
0582         caused no modification, that is logged.
0583 
0584         :return: Modifying rule or None.
0585         """
0586         fqn = parents + "::" + original["name"] + "[" + str(item.extent.start.line) + "]"
0587         self._trace_result(fqn, original, modified)
0588 
0589     def _trace_result(self, fqn, original, modified):
0590         """
0591         Record any modification both in the log and the returned result. If a rule fired, but
0592         caused no modification, that is logged.
0593 
0594         :return: Modifying rule or None.
0595         """
0596         if not modified["name"]:
0597             logger.debug(_("Rule {} suppressed {}, {}").format(self, fqn, original))
0598         else:
0599             delta = False
0600             for k, v in original.items():
0601                 if v != modified[k]:
0602                     delta = True
0603                     break
0604             if delta:
0605                 logger.debug(_("Rule {} modified {}, {}->{}").format(self, fqn, original, modified))
0606             else:
0607                 logger.warn(_("Rule {} did not modify {}, {}").format(self, fqn, original))
0608                 return None
0609         return self
0610 
0611     @abstractmethod
0612     def dump_usage(self, fn):
0613         raise NotImplemented(_("Missing subclass"))
0614 
0615     def __str__(self):
0616         return self.name
0617 
0618 class MethodCodeDb(AbstractCompiledCodeDb):
0619     """
0620     THE RULES FOR INJECTING METHOD-RELATED CODE (such as %MethodCode,
0621     %VirtualCatcherCode, %VirtualCallCode and other method-level directives).
0622 
0623     These are used to customize the behaviour of the SIP generator by allowing
0624     method-level code injection.
0625 
0626     The raw rule database must be an outer dictionary as follows:
0627 
0628         0. Each key is the fully-qualified name of a "container" enclosing
0629         methods.
0630 
0631         1. Each value is an inner dictionary, each of whose keys is the name
0632         of a method.
0633 
0634     Each inner dictionary has entries which update the declaration as follows:
0635 
0636         "name":         Optional string. If present, overrides the name of the
0637                         method.
0638         "parameters":   Optional list. If present, update the argument list.
0639 
0640         "fn_result":    Optional string. If present, update the return type.
0641 
0642         "code":         Required. Either a string, with the %XXXCode content,
0643                         or a callable.
0644 
0645     In use, the database is directly indexed by "container" and then method
0646     name. If "code" entry is a string, then the other optional keys are
0647     interpreted as above. If "code" is a callable, it is called with the
0648     following contract:
0649 
0650         def methodcode_xxx(function, sip, entry):
0651             '''
0652             Return a modified declaration for the given function.
0653 
0654             :param function:    The clang.cindex.Cursor for the function.
0655             :param sip:         A dict with keys as for function rules and (string)
0656                                 "code" keys described above.
0657             :param entry:       The inner dictionary entry.
0658 
0659             :return: An updated set of sip.xxx values.
0660             '''
0661 
0662     :return: The compiled form of the rules.
0663     """
0664     __metaclass__ = ABCMeta
0665 
0666     def __init__(self, db):
0667         super(MethodCodeDb, self).__init__(db)
0668 
0669         for k, v in self.db.items():
0670             for l in v.keys():
0671                 v[l]["usage"] = 0
0672 
0673     def _get(self, item, name):
0674 
0675         parents = _parents(item)
0676         entries = self.db.get(parents, None)
0677         if not entries:
0678             return None
0679 
0680         entry = entries.get(name, None)
0681         if not entry:
0682             return None
0683         entry["usage"] += 1
0684         return entry
0685 
0686     def apply(self, function, sip):
0687         """
0688         Walk over the code database for functions, applying the first matching transformation.
0689 
0690         :param function:            The clang.cindex.Cursor for the function.
0691         :param sip:                 The SIP dict (may be modified on return).
0692         :return:                    Modifying rule or None (even if a rule matched, it may not modify things).
0693         """
0694         entry = self._get(function, sip["name"])
0695         sip.setdefault("code", "")
0696         if entry:
0697             before = deepcopy(sip)
0698             if callable(entry["code"]):
0699                 fn = entry["code"]
0700                 fn_file = os.path.basename(inspect.getfile(fn))
0701                 trace = "// Generated (by {}:{}): {}\n".format(fn_file, fn.__name__, {k:v for (k,v) in entry.items() if k != "code"})
0702                 fn(function, sip, entry)
0703             else:
0704                 trace = "// Inserted (by {}:{}): {}\n".format(_parents(function), function.spelling, {k:v for (k,v) in entry.items() if k != "code"})
0705                 sip["code"] = entry["code"]
0706                 sip["parameters"] = entry.get("parameters", sip["parameters"])
0707                 sip["fn_result"] = entry.get("fn_result", sip["fn_result"])
0708             #
0709             # Fetch/format the code.
0710             #
0711             sip["code"] = trace + textwrap.dedent(sip["code"]).strip() + "\n"
0712             return self.trace_result(_parents(function), function, before, sip)
0713         return None
0714 
0715     def dump_usage(self, fn):
0716         """ Dump the usage counts."""
0717         for k in sorted(self.db.keys()):
0718             vk = self.db[k]
0719             for l in sorted(vk.keys()):
0720                 vl = vk[l]
0721                 fn(str(self) + " for " + k + "," + l, vl["usage"])
0722 
0723 class ModuleCodeDb(AbstractCompiledCodeDb):
0724     """
0725     THE RULES FOR INJECTING MODULE-RELATED CODE (such as %ExportedHeaderCode,
0726     %ModuleCode, %ModuleHeaderCode or other module-level directives).
0727 
0728     These are used to customize the behaviour of the SIP generator by allowing
0729     module-level code injection.
0730 
0731     The raw rule database must be a dictionary as follows:
0732 
0733         0. Each key is the basename of a header file.
0734 
0735         1. Each value has entries which update the declaration as follows:
0736 
0737         "code":         Required. Either a string, with the %XXXCode content,
0738                         or a callable.
0739 
0740     If "code" is a callable, it is called with the following contract:
0741 
0742         def module_xxx(filename, sip, entry):
0743             '''
0744             Return a string to insert for the file.
0745 
0746             :param filename:    The filename.
0747             :param sip:         A dict with the key "name" for the filename,
0748                                 "decl" for the module body plus the "code" key
0749                                 described above.
0750             :param entry:       The dictionary entry.
0751 
0752             :return: A string.
0753             '''
0754 
0755     :return: The compiled form of the rules.
0756     """
0757     __metaclass__ = ABCMeta
0758 
0759     def __init__(self, db):
0760         super(ModuleCodeDb, self).__init__(db)
0761         #
0762         # Add a usage count and other diagnostic support for each item in the database.
0763         #
0764         for k, v in self.db.items():
0765             v["usage"] = 0
0766 
0767     def _get(self, filename):
0768         #
0769         # Lookup for an actual hit.
0770         #
0771         entry = self.db.get(filename, None)
0772         if not entry:
0773             return None
0774         entry["usage"] += 1
0775         return entry
0776 
0777     def apply(self, filename, sip):
0778         """
0779         Walk over the code database for modules, applying the first matching transformation.
0780 
0781         :param filename:            The file for the module.
0782         :param sip:                 The SIP dict (may be modified on return).
0783         :return:                    Modifying rule or None (even if a rule matched, it may not modify things).
0784         """
0785         entry = self._get(filename)
0786         sip.setdefault("code", "")
0787         if entry:
0788             before = deepcopy(sip)
0789             if callable(entry["code"]):
0790                 fn = entry["code"]
0791                 fn_file = os.path.basename(inspect.getfile(fn))
0792                 trace = "\n// Generated (by {}:{}): {}".format(fn_file, fn.__name__, {k:v for (k,v) in entry.items() if k != "code"})
0793                 fn(filename, sip, entry)
0794                 sip["code"] = trace + sip["code"]
0795             else:
0796                 sip["code"] = entry["code"]
0797             #
0798             # Fetch/format the code.
0799             #
0800             sip["code"] = textwrap.dedent(sip["code"]).strip() + "\n"
0801             fqn = filename + "::" + before["name"]
0802             self._trace_result(fqn, before, sip)
0803 
0804     def dump_usage(self, fn):
0805         """ Dump the usage counts."""
0806         for k in sorted(self.db.keys()):
0807             v = self.db[k]
0808             fn(str(self) + " for " + k, v["usage"])
0809 
0810 
0811 class RuleSet(object):
0812     """
0813     To implement your own binding, create a subclass of RuleSet, also called
0814     RuleSet in your own Python module. Your subclass will expose the raw rules
0815     along with other ancillary data exposed through the subclass methods.
0816 
0817     You then simply run the SIP generation and SIP compilation programs passing
0818     in the name of your rules file
0819     """
0820     __metaclass__ = ABCMeta
0821 
0822     @abstractmethod
0823     def container_rules(self):
0824         """
0825         Return a compiled list of rules for containers.
0826 
0827         :return: A ContainerRuleDb instance
0828         """
0829         raise NotImplemented(_("Missing subclass implementation"))
0830 
0831     @abstractmethod
0832     def forward_declaration_rules(self):
0833         """
0834         Return a compiled list of rules for containers.
0835 
0836         :return: A ForwardDeclarationRuleDb instance
0837         """
0838         raise NotImplemented(_("Missing subclass implementation"))
0839 
0840     @abstractmethod
0841     def function_rules(self):
0842         """
0843         Return a compiled list of rules for functions.
0844 
0845         :return: A FunctionRuleDb instance
0846         """
0847         raise NotImplemented(_("Missing subclass implementation"))
0848 
0849     @abstractmethod
0850     def parameter_rules(self):
0851         """
0852         Return a compiled list of rules for function parameters.
0853 
0854         :return: A ParameterRuleDb instance
0855         """
0856         raise NotImplemented(_("Missing subclass implementation"))
0857 
0858     @abstractmethod
0859     def typedef_rules(self):
0860         """
0861         Return a compiled list of rules for typedefs.
0862 
0863         :return: A TypedefRuleDb instance
0864         """
0865         raise NotImplemented(_("Missing subclass implementation"))
0866 
0867     @abstractmethod
0868     def variable_rules(self):
0869         """
0870         Return a compiled list of rules for variables.
0871 
0872         :return: A VariableRuleDb instance
0873         """
0874         raise NotImplemented(_("Missing subclass implementation"))
0875 
0876     @abstractmethod
0877     def methodcode_rules(self):
0878         """
0879         Return a compiled list of rules for method-related code.
0880 
0881         :return: A MethodCodeDb instance
0882         """
0883         raise NotImplemented(_("Missing subclass implementation"))
0884 
0885     @abstractmethod
0886     def modulecode_rules(self):
0887         """
0888         Return a compiled list of rules for module-related code.
0889 
0890         :return: A ModuleCodeDb instance
0891         """
0892         raise NotImplemented(_("Missing subclass implementation"))
0893 
0894     def dump_unused(self):
0895         """Usage statistics, to identify unused rules."""
0896         def dumper(rule, usage):
0897             if usage:
0898                 logger.info(_("Rule {} used {} times".format(rule, usage)))
0899             else:
0900                 logger.warn(_("Rule {} was not used".format(rule)))
0901 
0902         for db in [self.container_rules(), self.forward_declaration_rules(), self.function_rules(),
0903                    self.parameter_rules(), self.typedef_rules(),
0904                    self.variable_rules(), self.methodcode_rules(), self.modulecode_rules()]:
0905             db.dump_usage(dumper)
0906 
0907     @abstractmethod
0908     def methodcode(self, container, function):
0909         """
0910         Lookup %MethodCode.
0911         """
0912         raise NotImplemented(_("Missing subclass implementation"))
0913 
0914     @abstractmethod
0915     def modulecode(self, filename):
0916         """
0917         Lookup %ModuleCode and friends.
0918         """
0919         raise NotImplemented(_("Missing subclass implementation"))
0920 
0921 
0922 def container_discard(container, sip, matcher):
0923     sip["name"] = ""
0924 
0925 def function_discard(container, function, sip, matcher):
0926     sip["name"] = ""
0927 
0928 def parameter_transfer_to_parent(container, function, parameter, sip, matcher):
0929     if function.is_static_method():
0930         sip["annotations"].add("Transfer")
0931     else:
0932         sip["annotations"].add("TransferThis")
0933 
0934 def param_rewrite_mode_t_as_int(container, function, parameter, sip, matcher):
0935     sip["decl"] = sip["decl"].replace("mode_t", "unsigned int")
0936 
0937 def return_rewrite_mode_t_as_int(container, function, sip, matcher):
0938     sip["fn_result"] = "unsigned int"
0939 
0940 def variable_discard(container, variable, sip, matcher):
0941     sip["name"] = ""
0942 
0943 def parameter_strip_class_enum(container, function, parameter, sip, matcher):
0944     sip["decl"] = sip["decl"].replace("class ", "").replace("enum ", "")
0945 
0946 def function_discard_impl(container, function, sip, matcher):
0947     if function.extent.start.column == 1:
0948         sip["name"] = ""
0949 
0950 def typedef_discard(container, typedef, sip, matcher):
0951     sip["name"] = ""
0952 
0953 def discard_QSharedData_base(container, sip, matcher):
0954     sip["base_specifiers"].remove("QSharedData")
0955 
0956 def mark_forward_declaration_external(container, sip, matcher):
0957     sip["annotations"].add("External")
0958 
0959 def container_mark_abstract(container, sip, matcher):
0960     sip["annotations"].add("Abstract")
0961 
0962 def rules(project_rules):
0963     """
0964     Constructor.
0965 
0966     :param project_rules:       The rules file for the project.
0967     """
0968     import imp
0969     imp.load_source("project_rules", project_rules)
0970     #
0971     # Statically prepare the rule logic. This takes the rules provided by the user and turns them into code.
0972     #
0973     return getattr(sys.modules["project_rules"], "RuleSet")()