def document_configuration(cls, ruleset="std"): """Add a 'Configuration' section to a Rule docstring. Utilize the the metadata in config_info to dynamically document the configuration options for a given rule. This is a little hacky, but it allows us to propagate configuration options in the docs, from a single source of truth. """ if ruleset == "std": config_info = get_config_info() else: # pragma: no cover raise ( NotImplementedError( "Add another config info dict for the new ruleset here!" ) ) config_doc = "\n **Configuration**\n" try: for keyword in sorted(cls.config_keywords): try: info_dict = config_info[keyword] except KeyError: # pragma: no cover raise KeyError( "Config value {!r} for rule {} is not configured in " "`config_info`.".format(keyword, cls.__name__) ) config_doc += "\n * ``{}``: {}".format(keyword, info_dict["definition"]) if ( config_doc[-1] != "." and config_doc[-1] != "?" and config_doc[-1] != "\n" ): config_doc += "." if "validation" in info_dict: config_doc += " Must be one of ``{}``.".format(info_dict["validation"]) except AttributeError: rules_logger.info(f"No config_keywords defined for {cls.__name__}") return cls # Add final blank line config_doc += "\n" if "**Anti-pattern**" in cls.__doc__: # Match `**Anti-pattern**`, then insert configuration before # the first occurrences pattern = re.compile("(\\s{4}\\*\\*Anti-pattern\\*\\*)", flags=re.MULTILINE) cls.__doc__ = pattern.sub(f"\n{config_doc}\n\\1", cls.__doc__, count=1) else: # Match last `\n` or `.`, then append configuration pattern = re.compile("(\\.|\\n)$", flags=re.MULTILINE) cls.__doc__ = pattern.sub(f"\\1\n{config_doc}\n", cls.__doc__, count=1) return cls
def document_configuration(cls, ruleset="std"): """Add a 'Configuration' section to a Rule docstring. Utilize the the metadata in config_info to dynamically document the configuration options for a given rule. This is a little hacky, but it allows us to propagate configuration options in the docs, from a single source of truth. """ if ruleset == "std": config_info = get_config_info() else: raise ( NotImplementedError( "Add another config info dict for the new ruleset here!" ) ) config_doc = "\n | **Configuration**" try: for keyword in sorted(cls.config_keywords): try: info_dict = config_info[keyword] except KeyError: raise KeyError( "Config value {!r} for rule {} is not configured in `config_info`.".format( keyword, cls.__name__ ) ) config_doc += "\n | `{0}`: {1}.".format( keyword, info_dict["definition"] ) if "validation" in info_dict: config_doc += " Must be one of {0}.".format(info_dict["validation"]) config_doc += "\n |" except AttributeError: rules_logger.info("No config_keywords defined for {0}".format(cls.__name__)) return cls # Add final blank line config_doc += "\n" # Add the configuration section immediately after the class description # docstring by inserting after the first line break, or first period, # if there is no line break. end_of_class_description = "." if "\n" not in cls.__doc__ else "\n" cls.__doc__ = cls.__doc__.replace(end_of_class_description, "\n" + config_doc, 1) return cls