aboutsummaryrefslogtreecommitdiffstats
path: root/parse_type/cfparse.py
blob: 1032a8744c97fd5687b714259fa622c910b6ee13 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# -*- coding: utf-8 -*-
"""
Provides an extended :class:`parse.Parser` class that supports the
cardinality fields in (user-defined) types.
"""

from __future__ import absolute_import
import logging
import parse
from .cardinality_field import CardinalityField, CardinalityFieldTypeBuilder
from .parse_util import FieldParser


log = logging.getLogger(__name__)   # pylint: disable=invalid-name


class Parser(parse.Parser):
    """Provides an extended :class:`parse.Parser` with cardinality field support.
    A cardinality field is a type suffix for parse format expression, ala:

        "... {person:Person?} ..."   -- OPTIONAL: Cardinality zero or one, 0..1
        "... {persons:Person*} ..."  -- MANY0: Cardinality zero or more, 0..
        "... {persons:Person+} ..."  -- MANY:  Cardinality one  or more, 1..

    When the primary type converter for cardinality=1 is provided,
    the type variants for the other cardinality cases can be derived from it.

    This parser class automatically creates missing type variants for types
    with a cardinality field and passes the extended type dictionary
    to its base class.
    """
    # -- TYPE-BUILDER: For missing types in Fields with CardinalityField part.
    type_builder = CardinalityFieldTypeBuilder

    def __init__(self, schema, extra_types=None, case_sensitive=False,
                 type_builder=None):
        """Creates a parser with CardinalityField part support.

        :param schema:  Parse schema (or format) for parser (as string).
        :param extra_types:  Type dictionary with type converters (or None).
        :param case_sensitive: Indicates if case-sensitive regexp are used.
        :param type_builder: Type builder to use for missing types.
        """
        if extra_types is None:
            extra_types = {}
        missing = self.create_missing_types(schema, extra_types, type_builder)
        if missing:
            # pylint: disable=logging-not-lazy
            log.debug("MISSING TYPES: %s" % ",".join(missing.keys()))
            extra_types.update(missing)

        # -- FINALLY: Delegate to base class.
        super(Parser, self).__init__(schema, extra_types,
                                     case_sensitive=case_sensitive)

    @classmethod
    def create_missing_types(cls, schema, type_dict, type_builder=None):
        """Creates missing types for fields with a CardinalityField part.
        It is assumed that the primary type converter for cardinality=1
        is registered in the type dictionary.

        :param schema:  Parse schema (or format) for parser (as string).
        :param type_dict:  Type dictionary with type converters.
        :param type_builder: Type builder to use for missing types.
        :return: Type dictionary with missing types. Empty, if none.
        :raises: MissingTypeError,
                if a primary type converter with cardinality=1 is missing.
        """
        if not type_builder:
            type_builder = cls.type_builder

        missing = cls.extract_missing_special_type_names(schema, type_dict)
        return type_builder.create_type_variants(missing, type_dict)

    @staticmethod
    def extract_missing_special_type_names(schema, type_dict):
        # pylint: disable=invalid-name
        """Extract the type names for fields with CardinalityField part.
        Selects only the missing type names that are not in the type dictionary.

        :param schema:     Parse schema to use (as string).
        :param type_dict:  Type dictionary with type converters.
        :return: Generator with missing type names (as string).
        """
        for name in FieldParser.extract_types(schema):
            if CardinalityField.matches_type(name) and (name not in type_dict):
                yield name