def get_regex_padding(padmin=None, padmax=None): """ Gets a regex pattern for enforcing padding size on other patterns. Defaults to an unlimited size (minimum 1) if neither value is provided. Args: padmin (int): Minimum number of characters required padmax (int): Maximum number of characters allowed Returns: str: Regex padding symbol(s) to append to a regex pattern """ if padmin is not None and padmin < 0: raise exceptions.ResolverError( "Padmin cannot be less than 0: {}".format(padmin)) if padmax is not None and padmax < 0: raise exceptions.ResolverError( "Padmax cannot be less than 0: {}".format(padmax)) if padmin is not None and padmax is not None: if padmax < padmin: raise exceptions.ResolverError( "Padmax ({}) cannot be lower than padmin ({})".format( padmax, padmin)) padding_str = "{%d,%d}" % (padmin, padmax) elif padmin is not None: padding_str = "{%d,}" % padmin elif padmax is not None: padding_str = "{,%d}" % padmax else: padding_str = "+" return padding_str
def create_token(self, token_name, token_config): """ Raises: exceptions.ResolverError: If the token data is invalid Args: token_name (str): Name of the token to create token_config (dict): Dictionary of token data, with a minimum of a "type" key. Returns: token.Token: Created token object stored in the resolver """ if token_name in self._tokens: raise exceptions.ResolverError( "Token '{}' already exists".format(token_name) ) token_type = token_config[constants.KEY_TYPE] token_cls = self.get_token_cls(token_type) regex = token_cls.get_regex_from_config(token_config) format_spec = token_cls.get_format_spec_from_config(token_config) description = token_cls.get_description_from_config(token_config) default = token_config.get("default") token_obj = token_cls( token_name, regex=regex, format_spec=format_spec, description=description, default=default, ) self._tokens[token_name] = token_obj return token_obj
def get_token_cls(cls, token_type): """ Args: token_type (str): String name of the token type Returns: Type[token.Token]: Token class the name represents """ if token_type == constants.TokenType.Int: token_cls = token.IntToken elif token_type == constants.TokenType.String: token_cls = token.StringToken else: raise exceptions.ResolverError("Unknown token type: {}".format(token_type)) return token_cls
def token(self, name): """ Raises: exceptions.ResolverError: If no token exists matching the name Args: name (str): Name of the token to get Returns: token.Token: """ token_obj = self._tokens.get(name) if token_obj is None: raise exceptions.ResolverError( "Requested token name does not exist: {}".format(name) ) return token_obj
def template(self, name): """ Raises: exceptions.ResolverError: If no template exists matching the name Args: name (str): Name of the template to get Returns: template.Template: """ template_obj = self._templates.get(name) if template_obj is None: raise exceptions.ResolverError( "Requested template name does not exist: {}".format(name) ) return template_obj
def get_template_cls(cls, template_type): """ Args: template_type (str): String name of the template type Returns: Type[token.Template]: Template class the name represents """ if template_type == constants.TemplateType.Standard: token_cls = template.Template elif template_type == constants.TemplateType.Path: token_cls = pathtemplate.PathTemplate else: raise exceptions.ResolverError( "Unknown template type: {}".format(template_type) ) return token_cls
def get_regex_from_config(cls, config): """ Args: config (dict): Dictionary of token configuration values Returns: str: Regex pattern for the token """ regex = config.get("regex") choices = config.get("choices") padmin = config.get("padmin") padmax = config.get("padmax") if regex is None: if choices: regex = "|".join(map(str, choices)) else: regex = cls.REGEX regex += util.get_regex_padding(padmin=padmin, padmax=padmax) elif choices or padmin or padmax: raise exceptions.ResolverError( "Cannot use construction keywords with explicit regex") return regex
def create_template(self, template_name, template_config, reference_config=None): """ Raises: exceptions.ResolverError: If the template string references a non-existent value Args: template_name (str): Name of the template to create template_config (dict): Dictionary of template data with a minimum of a "type" and "string" key Keyword Args: reference_config (dict): Dictionary of template names mapped to template configs. If creating a template which references additional templates that does not exist yet, the resolver will try to recursively construct templates from this config. Returns: template.Template: Created template object stored in the resolver """ if template_name in self._templates: raise exceptions.ResolverError( "Template '{}' already exists".format(template_name) ) template_type = template_config[constants.KEY_TYPE] template_string = template_config[constants.KEY_STRING] index = 0 segments = [] for match in re.finditer(constants.TOKEN_PATTERN, template_string): # Extract fixed string segments between token/templates start, end = match.span() if start != index: segments.append(template_string[index:start]) index = end # Find the matching referenced object symbol, name = match.groups() if symbol == constants.SYMBOL_TEMPLATE: try: template_obj = self.template(name) except exceptions.ResolverError: if reference_config is None or name not in reference_config: raise template_obj = self.create_template( name, reference_config[name], reference_config=reference_config ) segments.append(template_obj) elif not symbol: token_obj = self.token(name) segments.append(token_obj) else: raise exceptions.ResolverError( "Unknown token symbol: {}".format(symbol) ) # If it ends with a fixed string, ensure the remainder is added last_string_segment = template_string[index:] if last_string_segment: segments.append(last_string_segment) template_cls = self.get_template_cls(template_type) template_obj = template_cls(template_name, segments) self._templates[template_name] = template_obj return template_obj
class StringToken(Token): PADALIGN = constants.DEFAULT_PADALIGN_STR PADCHAR = constants.DEFAULT_PADCHAR_STR REGEX = constants.REGEX_STR @classmethod def get_description_from_config(cls, config): """ Args: config (dict): Dictionary of token configuration values Returns: str: Description message for the token """ description = super(StringToken, cls).get_description_from_config(config) if description is None: padmin = config.get("padmin") padmax = config.get("padmax") case = config.get("case") case = "{} case ".format(case) if case else "" if padmin is not None and padmin == padmax: description = "Must be a {}-character {}string".format( padmin, case) elif padmin is not None: description = "Must be a minimum {}-character {}string".format( padmin, case) elif padmax is not None: description = "Must be a maximum {}-character {}string".format( padmax, case) else: description = "Must be a {}string".format(case) return description @classmethod def get_format_spec_from_config(cls, config): """ Args: config (dict): Dictionary of token configuration values Returns: str: Format spec for the token with the string "s" appended """ # Enable strict padding by default unless set config.setdefault("padstrict", True) format_spec = super(StringToken, cls).get_format_spec_from_config(config) return format_spec + "s" @classmethod def get_regex_from_config(cls, config): """ Args: config (dict): Dictionary of token configuration values Returns: str: Regex pattern for the token """ case = config.get("case") regex = config.get("regex") if regex is None and case: regex = util.get_case_regex(case) padmin = config.get("padmin") padmax = config.get("padmax") # Camel cases add a fixed starting character, modify padding accordingly if case in (constants.Case.LowerCamel, constants.Case.UpperCamel): padmin = (padmin - 1) if padmin else None padmax = (padmax - 1) if padmax else None regex += util.get_regex_padding(padmin=padmin, padmax=padmax) elif case is not None: raise exceptions.ResolverError( "Cannot use construction keywords with explicit regex")