Exemplo n.º 1
0
def generate_choices(rule):
    choices = []

    # Split rule into tokens, ignoring white space.
    tokens = _tools.tokenize_without_space(rule)

    # Extract choices from rule tokens.
    # TODO: Handle comma after comma without choice.
    # previous_toky = None
    toky = next(tokens)
    while not _tools.is_eof_token(toky):
        if _tools.is_comma_token(toky):
            # TODO: Handle comma after comma without choice.
            # if previous_toky:
            #     previous_toky_text = previous_toky[1]
            # else:
            #     previous_toky_text = None
            pass
        choice = _tools.token_text(toky)
        choices.append(choice)
        toky = next(tokens)
        if not _tools.is_eof_token(toky):
            # Process next choice after comma.
            toky = next(tokens)

    return choices
Exemplo n.º 2
0
    def __init__(self, field_name, is_allowed_to_be_empty, length, rule, data_format):
        super(ConstantFieldFormat, self).__init__(
            field_name, is_allowed_to_be_empty, length, rule, data_format, empty_value='')

        # Extract constant from rule tokens.
        tokens = _tools.tokenize_without_space(rule)
        toky = next(tokens)
        if _tools.is_eof_token(toky):
            # No rule means that the field must always be empty.
            self._constant = ''
        else:
            self._constant = _tools.token_text(toky)
            toky = next(tokens)
            if not _tools.is_eof_token(toky):
                raise errors.InterfaceError(
                    'constant rule must be a single Python token but also found: %s'
                    % _compat.text_repr(_tools.token_text(toky)))
        has_empty_rule = (rule == '')
        if self.is_allowed_to_be_empty and not has_empty_rule:
            raise errors.InterfaceError(
                'to describe a Constant that can be empty, use a Choice field with a single choice')
        if not self.is_allowed_to_be_empty and has_empty_rule:
            raise errors.InterfaceError(
                'field must be marked as empty to describe a constant empty value')
        try:
            self.length.validate(
                'rule of constant field %s' % _compat.text_repr(self.field_name), len(self._constant))
        except errors.RangeValueError:
            raise errors.InterfaceError(
                'length is %s but must be %d to match constant %s'
                % (self.length, len(self._constant), _compat.text_repr(self._constant)))
Exemplo n.º 3
0
    def __init__(self, field_name, is_allowed_to_be_empty, length, rule, data_format):
        super(ConstantFieldFormat, self).__init__(
            field_name, is_allowed_to_be_empty, length, rule, data_format, empty_value='')

        # Extract constant from rule tokens.
        tokens = _tools.tokenize_without_space(rule)
        toky = next(tokens)
        if _tools.is_eof_token(toky):
            # No rule means that the field must always be empty.
            self._constant = ''
        else:
            self._constant = _tools.token_text(toky)
            toky = next(tokens)
            if not _tools.is_eof_token(toky):
                raise errors.InterfaceError(
                    _('constant rule must be a single Python token but also found: %s')
                    % _compat.text_repr(_tools.token_text(toky)))
        has_empty_rule = (rule == '')
        if self.is_allowed_to_be_empty and not has_empty_rule:
            raise errors.InterfaceError(
                _('to describe a Constant that can be empty, use a Choice field with a single choice'))
        if not self.is_allowed_to_be_empty and has_empty_rule:
            raise errors.InterfaceError(
                _('field must be marked as empty to describe a constant empty value'))
        try:
            self.length.validate(
                _('rule of constant field %s') % _compat.text_repr(self.field_name), len(self._constant))
        except errors.RangeValueError:
            raise errors.InterfaceError(
                _('length is %s but must be %d to match constant %s')
                % (self.length, len(self._constant), _compat.text_repr(self._constant)))
Exemplo n.º 4
0
    def _validated_character(key, value, location):
        r"""
        A single character intended as value for data format property ``key``
        derived from ``value``, which can be:

        * a decimal or hex number (prefixed with ``'0x'``) referring to the ASCII/Unicode of the character
        * a string containing a single character such as ``'\t'``.
        * a symbolic name from :py:const:`cutplace.errors.NAME_TO_ASCII_CODE_MAP` such as ``tab``.

        :raises cutplace.errors.InterfaceError: on any broken ``value``
        """
        assert key
        assert value is not None

        name_for_errors = 'data format property %s' % _compat.text_repr(key)
        stripped_value = value.strip()
        if (len(stripped_value) == 1) and (stripped_value
                                           not in string.digits):
            result_code = ord(stripped_value)
        else:
            tokens = tokenize.generate_tokens(io.StringIO(value).readline)
            next_token = next(tokens)
            if _tools.is_eof_token(next_token):
                raise errors.InterfaceError(
                    _("value for %s must be specified") % name_for_errors,
                    location)
            next_type = next_token[0]
            next_value = next_token[1]
            if next_type == token.NAME:
                result_code = ranges.code_for_symbolic_token(
                    name_for_errors, next_value, location)
            elif next_type == token.NUMBER:
                result_code = ranges.code_for_number_token(
                    name_for_errors, next_value, location)
            elif next_type == token.STRING:
                result_code = ranges.code_for_string_token(
                    name_for_errors, next_value, location)
            elif (len(next_value)
                  == 1) and not _tools.is_eof_token(next_token):
                result_code = ord(next_value)
            else:
                raise errors.InterfaceError(
                    _('value for %s must a number, a single character or a symbolic name but is: %s'
                      ) % (name_for_errors, _compat.text_repr(value)),
                    location)
            # Ensure there are no further tokens.
            next_token = next(tokens)
            if (not _tools.is_eof_token(next_token)) and (next_token[0] !=
                                                          tokenize.NEWLINE):
                raise errors.InterfaceError(
                    _('value for %s must be a single character but is: %s') %
                    (name_for_errors, _compat.text_repr(value)), location)
        # TODO: Handle 'none' properly.
        assert result_code is not None
        assert result_code >= 0
        result = six.unichr(result_code)
        assert result is not None
        return result
Exemplo n.º 5
0
    def _validated_character(key, value, location):
        r"""
        A single character intended as value for data format property ``key``
        derived from ``value``, which can be:

        * a decimal or hex number (prefixed with ``'0x'``) referring to the ASCII/Unicode of the character
        * a string containing a single character such as ``'\t'``.
        * a symbolic name from :py:const:`cutplace.errors.NAME_TO_ASCII_CODE_MAP` such as ``tab``.

        :raises cutplace.errors.InterfaceError: on any broken ``value``
        """
        assert key
        assert value is not None

        name_for_errors = 'data format property %s' % _compat.text_repr(key)
        stripped_value = value.strip()
        if (len(stripped_value) == 1) and (stripped_value not in string.digits):
            result_code = ord(stripped_value)
        else:
            tokens = tokenize.generate_tokens(io.StringIO(value).readline)
            next_token = next(tokens)
            if _tools.is_eof_token(next_token):
                raise errors.InterfaceError(
                    "value for %s must be specified" % name_for_errors, location)
            next_type = next_token[0]
            next_value = next_token[1]
            if next_type == token.NAME:
                result_code = ranges.code_for_symbolic_token(name_for_errors, next_value, location)
            elif next_type == token.NUMBER:
                result_code = ranges.code_for_number_token(name_for_errors, next_value, location)
            elif next_type == token.STRING:
                result_code = ranges.code_for_string_token(name_for_errors, next_value, location)
            elif (len(next_value) == 1) and not _tools.is_eof_token(next_token):
                result_code = ord(next_value)
            else:
                raise errors.InterfaceError(
                    'value for %s must a number, a single character or a symbolic name but is: %s'
                    % (name_for_errors, _compat.text_repr(value)), location)
            # Ensure there are no further tokens.
            next_token = next(tokens)
            if not _tools.is_eof_token(next_token):
                raise errors.InterfaceError(
                    'value for %s must be a single character but is: %s'
                    % (name_for_errors, _compat.text_repr(value)), location)
        # TODO: Handle 'none' properly.
        assert result_code is not None
        assert result_code >= 0
        result = six.unichr(result_code)
        assert result is not None
        return result
Exemplo n.º 6
0
    def __init__(self, field_name, is_allowed_to_be_empty, length, rule,
                 data_format):
        super(ChoiceFieldFormat, self).__init__(field_name,
                                                is_allowed_to_be_empty,
                                                length,
                                                rule,
                                                data_format,
                                                empty_value='')
        self.choices = []

        # Split rule into tokens, ignoring white space.
        tokens = _tools.tokenize_without_space(rule)

        # Extract choices from rule tokens.
        previous_toky = None
        toky = next(tokens)
        while not _tools.is_eof_token(toky):
            if _tools.is_comma_token(toky):
                # Handle comma after comma without choice.
                if previous_toky:
                    previous_toky_text = previous_toky[1]
                else:
                    previous_toky_text = None
                raise errors.InterfaceError(
                    "choice value must precede a comma (,) but found: %s" %
                    _compat.text_repr(previous_toky_text))
            choice = _tools.token_text(toky)
            if not choice:
                raise errors.InterfaceError(
                    "choice field must be allowed to be empty instead of containing an empty choice"
                )
            self.choices.append(choice)
            toky = next(tokens)
            if not _tools.is_eof_token(toky):
                if not _tools.is_comma_token(toky):
                    raise errors.InterfaceError(
                        "comma (,) must follow choice value %s but found: %s" %
                        (_compat.text_repr(choice), _compat.text_repr(
                            toky[1])))
                # Process next choice after comma.
                toky = next(tokens)
                if _tools.is_eof_token(toky):
                    raise errors.InterfaceError(
                        "trailing comma (,) must be removed")
        if not self.is_allowed_to_be_empty and not self.choices:
            raise errors.InterfaceError(
                "choice field without any choices must be allowed to be empty")
Exemplo n.º 7
0
    def __init__(self, field_name, is_allowed_to_be_empty, length, rule, data_format):
        super(ChoiceFieldFormat, self).__init__(
            field_name, is_allowed_to_be_empty, length, rule, data_format, empty_value='')
        self.choices = []

        # Split rule into tokens, ignoring white space.
        tokens = _tools.tokenize_without_space(rule)

        # Extract choices from rule tokens.
        previous_toky = None
        toky = next(tokens)
        while not _tools.is_eof_token(toky):
            if _tools.is_comma_token(toky):
                # Handle comma after comma without choice.
                if previous_toky:
                    previous_toky_text = previous_toky[1]
                else:
                    previous_toky_text = None
                raise errors.InterfaceError(
                    "choice value must precede a comma (,) but found: %s" % _compat.text_repr(previous_toky_text))
            choice = _tools.token_text(toky)
            if not choice:
                raise errors.InterfaceError(
                    "choice field must be allowed to be empty instead of containing an empty choice")
            self.choices.append(choice)
            toky = next(tokens)
            if not _tools.is_eof_token(toky):
                if not _tools.is_comma_token(toky):
                    raise errors.InterfaceError(
                        "comma (,) must follow choice value %s but found: %s"
                        % (_compat.text_repr(choice), _compat.text_repr(toky[1])))
                # Process next choice after comma.
                toky = next(tokens)
                if _tools.is_eof_token(toky):
                    raise errors.InterfaceError("trailing comma (,) must be removed")
        if not self.is_allowed_to_be_empty and not self.choices:
            raise errors.InterfaceError("choice field without any choices must be allowed to be empty")
Exemplo n.º 8
0
    def __init__(self,
                 description,
                 rule,
                 available_field_names,
                 location=None):
        super(IsUniqueCheck, self).__init__(description, rule,
                                            available_field_names, location)

        self._field_names_to_check = []
        self._row_key_to_location_map = None
        self.reset()

        # Extract field names to check from rule.
        rule_read_line = _compat.token_io_readline(rule)
        toky = tokenize.generate_tokens(rule_read_line)
        after_comma = True
        next_token = next(toky)
        unique_field_names = set()
        while (not _tools.is_eof_token(next_token)) and (next_token[0] !=
                                                         tokenize.NEWLINE):
            token_type = next_token[0]
            token_value = next_token[1]
            if after_comma:
                if token_type != tokenize.NAME:
                    raise errors.InterfaceError(
                        _("field name must contain only ASCII letters, numbers and underscores (_) "
                          "but found: %r [token type=%r]") %
                        (token_value, token_type), self.location_of_rule)
                try:
                    fields.field_name_index(token_value, available_field_names,
                                            location)
                    if token_value in unique_field_names:
                        raise errors.InterfaceError(
                            _("duplicate field name for unique check must be removed: %s"
                              ) % token_value, self.location_of_rule)
                    unique_field_names.add(token_value)
                except errors.InterfaceError as error:
                    raise errors.InterfaceError(six.text_type(error))
                self._field_names_to_check.append(token_value)
            elif not _tools.is_comma_token(next_token):
                raise errors.InterfaceError(
                    _("after field name a comma (,) must follow but found: %r")
                    % token_value, self.location_of_rule)
            after_comma = not after_comma
            next_token = next(toky)
        if not len(self._field_names_to_check):
            raise errors.InterfaceError(
                _("rule must contain at least one field name to check for uniqueness"
                  ), self.location_of_rule)
Exemplo n.º 9
0
    def __init__(self, description, rule, available_field_names, location=None):
        super(IsUniqueCheck, self).__init__(description, rule, available_field_names, location)

        self._field_names_to_check = []
        self._row_key_to_location_map = None
        self.reset()

        # Extract field names to check from rule.
        rule_read_line = _compat.token_io_readline(rule)
        toky = tokenize.generate_tokens(rule_read_line)
        after_comma = True
        next_token = next(toky)
        unique_field_names = set()
        while not _tools.is_eof_token(next_token):
            token_type = next_token[0]
            token_value = next_token[1]
            if after_comma:
                if token_type != tokenize.NAME:
                    raise errors.InterfaceError(
                        "field name must contain only ASCII letters, numbers and underscores (_) "
                        + "but found: %r [token type=%r]" % (token_value, token_type), self.location_of_rule)
                try:
                    fields.field_name_index(token_value, available_field_names, location)
                    if token_value in unique_field_names:
                        raise errors.InterfaceError(
                            "duplicate field name for unique check must be removed: %s" % token_value,
                            self.location_of_rule)
                    unique_field_names.add(token_value)
                except errors.InterfaceError as error:
                    raise errors.InterfaceError(six.text_type(error))
                self._field_names_to_check.append(token_value)
            elif not _tools.is_comma_token(next_token):
                raise errors.InterfaceError(
                    "after field name a comma (,) must follow but found: %r" % token_value, self.location_of_rule)
            after_comma = not after_comma
            next_token = next(toky)
        if not len(self._field_names_to_check):
            raise errors.InterfaceError(
                "rule must contain at least one field name to check for uniqueness", self.location_of_rule)
Exemplo n.º 10
0
    def __init__(self, description, default=None, location=None):
        """
        Setup a decimal range as specified by ``description``.

        :param str description: a range description of the form \
          ``lower...upper`` or ``limit``, possibly consisting of multiple \
          items. In case it is empty (``''``), the range specified by \
          ``default`` is used; the description also specifies the \
          :py:attr:`~cutplace.ranges.DecimalRange.scale` and \
          :py:attr:`~cutplace.ranges.DecimalRange.precision` valid numbers \
          can use.
        :param str default: an alternative to use in case ``description``
          is ``None`` or empty; in case both ``description`` and \
          ``default`` are ``None`` or empty, all values within the \
          :py:const:`DEFAULT_SCALE` and :py:const:`DEFAULT_PRECISION` are \
          valid.

        """
        assert default is None or (default.strip() !=
                                   ''), "default=%r" % default

        self._precision = DEFAULT_PRECISION
        self._scale = DEFAULT_SCALE

        # Find out if a `description` has been specified and if not, use optional `default` instead.
        has_description = (description
                           is not None) and (description.strip() != '')
        if not has_description and default is not None:
            description = default
            has_description = True

        if not has_description:
            # Use empty ranges.
            self._description = None
            self._items = None
            self._lower_limit = None
            self._upper_limit = None
        else:
            self._description = description.replace('...', ELLIPSIS)
            self._items = []
            tokens = _tools.tokenize_without_space(self._description)
            end_reached = False
            max_digits_after_dot = 0
            max_digits_before_dot = 0
            while not end_reached:
                lower = None
                upper = None
                ellipsis_found = False
                after_hyphen = False
                next_token = next(tokens)
                while not _tools.is_eof_token(
                        next_token) and not _tools.is_comma_token(next_token):
                    next_type = next_token[0]
                    next_value = next_token[1]
                    if next_type == token.NUMBER:
                        if next_type == token.NUMBER:
                            try:
                                decimal_value = decimal.Decimal(next_value)
                                _sign, digits, exponent = decimal_value.as_tuple(
                                )
                                digits_after_dot = max(0, -exponent)
                                if digits_after_dot > max_digits_after_dot:
                                    max_digits_after_dot = digits_after_dot
                                digits_before_dot = len(digits) + exponent
                                if digits_before_dot > max_digits_before_dot:
                                    max_digits_before_dot = digits_before_dot
                            except decimal.DecimalException:
                                raise errors.InterfaceError(
                                    _("number must be an decimal or integer but is: %s"
                                      ) % _compat.text_repr(next_value),
                                    location)
                            if after_hyphen:
                                decimal_value = decimal_value.copy_negate()
                                after_hyphen = False

                        if ellipsis_found:
                            if upper is None:
                                upper = decimal_value
                            else:
                                raise errors.InterfaceError(
                                    _("range must have at most lower and upper limit but found another number: %s"
                                      ) % _compat.text_repr(next_value),
                                    location)
                        elif lower is None:
                            lower = decimal_value
                        else:
                            raise errors.InterfaceError(
                                _("number must be followed by ellipsis (...) but found: %s"
                                  ) % _compat.text_repr(next_value))
                    elif after_hyphen:
                        raise errors.InterfaceError(
                            _("hyphen (-) must be followed by number but found: %s"
                              ) % _compat.text_repr(next_value))
                    elif (next_type == token.OP) and (next_value == "-"):
                        after_hyphen = True
                    elif next_value in (ELLIPSIS, ':'):
                        ellipsis_found = True
                    else:
                        message = "range must be specified using decimal or integer numbers" \
                                  " and ellipsis (...) but found: %s [token type: %d]" \
                                  % (_compat.text_repr(next_value), next_type)
                        raise errors.InterfaceError(message)
                    next_token = next(tokens)

                if after_hyphen:
                    raise errors.InterfaceError(
                        _("hyphen (-) at end must be followed by number"))

                # Decide upon the result.
                if lower is None:
                    if upper is None:
                        if ellipsis_found:
                            # Handle "...".
                            # TODO: Handle "..." same as ""?
                            raise errors.InterfaceError(
                                _("ellipsis (...) must be preceded and/or succeeded by number"
                                  ))

                    else:
                        assert ellipsis_found
                        # Handle "...y".
                        range_item = (None, upper)
                elif ellipsis_found:
                    # Handle "x..." and "x...y".
                    if (upper is not None) and (lower > upper):
                        raise errors.InterfaceError(
                            _("lower limit %s must be less or equal than upper limit %s"
                              ) % (_decimal_as_text(lower, self.precision),
                                   _decimal_as_text(upper, self.precision)))
                    range_item = (lower, upper)
                else:
                    # Handle "x".
                    range_item = (lower, lower)
                if range_item is not None:
                    self._precision = max_digits_after_dot
                    self._scale = max_digits_before_dot + max_digits_after_dot
                    for item in self._items:
                        if self._items_overlap(item, range_item):
                            item_text = _compat.text_repr(
                                self._repr_item(item))
                            result_text = _compat.text_repr(
                                self._repr_item(range_item))
                            raise errors.InterfaceError(
                                _("overlapping parts in decimal range must be cleaned up: %s and %s"
                                  ) % (item_text, result_text), location)
                    self._items.append(range_item)
                if _tools.is_eof_token(next_token):
                    end_reached = True

            assert self.precision >= 0
            assert self.scale >= self.precision

            self._lower_limit = None
            self._upper_limit = None
            is_first_item = True
            for lower_item, upper_item in self._items:
                if is_first_item:
                    self._lower_limit = lower_item
                    self._upper_limit = upper_item
                    is_first_item = False

                if lower_item is None:
                    self._lower_limit = None
                elif (self._lower_limit
                      is not None) and (lower_item < self._lower_limit):
                    self._lower_limit = lower_item

                if upper_item is None:
                    self._upper_limit = None
                elif (self._upper_limit
                      is not None) and (upper_item > self._upper_limit):
                    self._upper_limit = upper_item
Exemplo n.º 11
0
    def __init__(self, description, default=None):
        """
        Setup a range as specified by ``description``.

        :param str description: a range description of the form \
          ``lower...upper`` or ``limit``. In case it is empty (``''``), any \
          value will be accepted by \
          :py:meth:`~cutplace.ranges.Range.validate()`. For example, \
          ``1...40`` accepts values between 1 and 40.
        :param str default: an alternative to use in case ``description`` is \
          ``None`` or empty.
        """
        assert default is None or (default.strip() !=
                                   ''), "default=%r" % default

        # Find out if a `description` has been specified and if not, use optional `default` instead.
        has_description = (description
                           is not None) and (description.strip() != '')
        if not has_description and default is not None:
            description = default
            has_description = True

        if not has_description:
            # Use empty ranges.
            self._description = None
            self._items = None
            self._lower_limit = None
            self._upper_limit = None
        else:
            self._description = description.replace('...', ELLIPSIS)
            self._items = []

            name_for_code = 'range'
            location = None  # TODO: Add location where range is declared.
            tokens = _tools.tokenize_without_space(self._description)
            end_reached = False
            while not end_reached:
                lower = None
                upper = None
                ellipsis_found = False
                after_hyphen = False
                next_token = next(tokens)
                while not _tools.is_eof_token(
                        next_token) and not _tools.is_comma_token(next_token):
                    next_type = next_token[0]
                    next_value = next_token[1]
                    if next_type in (token.NAME, token.NUMBER, token.STRING):
                        if next_type == token.NAME:
                            # Symbolic names, e.g. ``tab``.
                            value_as_int = code_for_symbolic_token(
                                name_for_code, next_value, location)
                        elif next_type == token.NUMBER:
                            # Numbers, e.g. ``123``.
                            value_as_int = code_for_number_token(
                                name_for_code, next_value, location)
                            if after_hyphen:
                                value_as_int *= -1
                                after_hyphen = False
                        elif next_type == token.STRING:
                            # Python strings, e.g. ``'abc'`` or ``"""abc"""``.
                            value_as_int = code_for_string_token(
                                name_for_code, next_value, location)
                        elif (len(next_value)
                              == 1) and not _tools.is_eof_token(next_token):
                            # Other single characters, e.g. ``,``; this is particular useful with delimiter properties.
                            value_as_int = ord(next_value)
                        else:
                            raise errors.InterfaceError(
                                _('value for %s must a number, a single character or a symbolic name but is: %s'
                                  ) %
                                (name_for_code, _compat.text_repr(next_value)),
                                location)
                        if ellipsis_found:
                            if upper is None:
                                upper = value_as_int
                            else:
                                raise errors.InterfaceError(
                                    _("range must have at most lower and upper limit but found another number: %s"
                                      ) % _compat.text_repr(next_value),
                                    location)
                        elif lower is None:
                            lower = value_as_int
                        else:
                            raise errors.InterfaceError(
                                _("number must be followed by ellipsis (...) but found: %s"
                                  ) % _compat.text_repr(next_value), location)
                    elif after_hyphen:
                        raise errors.InterfaceError(
                            _("hyphen (-) must be followed by number but found: %s"
                              ) % _compat.text_repr(next_value), location)
                    elif (next_type == token.OP) and (next_value == "-"):
                        after_hyphen = True
                    elif next_value in (ELLIPSIS, ':'):
                        ellipsis_found = True
                    else:
                        raise errors.InterfaceError(
                            _("range must be specified using integer numbers, text, "
                              "symbols and ellipsis (...) but found: %s [token type: %d]"
                              ) % (_compat.text_repr(next_value), next_type),
                            location)
                    next_token = next(tokens)

                if after_hyphen:
                    raise errors.InterfaceError(
                        _("hyphen (-) at end must be followed by number"),
                        location)

                # Decide upon the result.
                if lower is None:
                    if upper is None:
                        if ellipsis_found:
                            # Handle "...".
                            raise errors.InterfaceError(
                                _('ellipsis (...) must be preceded and/or succeeded by number'
                                  ), location)
                        else:
                            # Handle "".
                            result = None
                    else:
                        assert ellipsis_found
                        # Handle "...y".
                        result = (None, upper)
                elif ellipsis_found:
                    # Handle "x..." and "x...y".
                    if (upper is not None) and (lower > upper):
                        raise errors.InterfaceError(
                            _("lower range %d must be greater or equal than upper range %d"
                              ) % (lower, upper), location)
                    result = (lower, upper)
                else:
                    # Handle "x".
                    result = (lower, lower)
                if result is not None:
                    for item in self._items:
                        if self._items_overlap(item, result):
                            item_text = _compat.text_repr(
                                self._repr_item(item))
                            result_text = _compat.text_repr(
                                self._repr_item(result))
                            raise errors.InterfaceError(
                                _("overlapping parts in range must be cleaned up: %s and %s"
                                  ) % (item_text, result_text), location)
                    self._items.append(result)
                if _tools.is_eof_token(next_token):
                    end_reached = True

            self._lower_limit = None
            self._upper_limit = None
            is_first_item = True
            for lower_item, upper_item in self._items:
                if is_first_item:
                    self._lower_limit = lower_item
                    self._upper_limit = upper_item
                    is_first_item = False

                if lower_item is None:
                    self._lower_limit = None
                elif (self._lower_limit
                      is not None) and (lower_item < self._lower_limit):
                    self._lower_limit = lower_item

                if upper_item is None:
                    self._upper_limit = None
                elif (self._upper_limit
                      is not None) and (upper_item > self._upper_limit):
                    self._upper_limit = upper_item
Exemplo n.º 12
0
    def __init__(self, description, default=None, location=None):
        """
        Setup a decimal range as specified by ``description``.

        :param str description: a range description of the form \
          ``lower...upper`` or ``limit``, possibly consisting of multiple \
          items. In case it is empty (``''``), the range specified by \
          ``default`` is used; the description also specifies the \
          :py:attr:`~cutplace.ranges.DecimalRange.scale` and \
          :py:attr:`~cutplace.ranges.DecimalRange.precision` valid numbers \
          can use.
        :param str default: an alternative to use in case ``description``
          is ``None`` or empty; in case both ``description`` and \
          ``default`` are ``None`` or empty, all values within the \
          :py:const:`DEFAULT_SCALE` and :py:const:`DEFAULT_PRECISION` are \
          valid.

        """
        assert default is None or (default.strip() != ''), "default=%r" % default

        self._precision = DEFAULT_PRECISION
        self._scale = DEFAULT_SCALE

        # Find out if a `description` has been specified and if not, use optional `default` instead.
        has_description = (description is not None) and (description.strip() != '')
        if not has_description and default is not None:
            description = default
            has_description = True

        if not has_description:
            # Use empty ranges.
            self._description = None
            self._items = None
            self._lower_limit = None
            self._upper_limit = None
        else:
            self._description = description.replace('...', ELLIPSIS)
            self._items = []
            tokens = _tools.tokenize_without_space(self._description)
            end_reached = False
            max_digits_after_dot = 0
            max_digits_before_dot = 0
            while not end_reached:
                lower = None
                upper = None
                ellipsis_found = False
                after_hyphen = False
                next_token = next(tokens)
                while not _tools.is_eof_token(next_token) and not _tools.is_comma_token(next_token):
                    next_type = next_token[0]
                    next_value = next_token[1]
                    if next_type == token.NUMBER:
                        if next_type == token.NUMBER:
                            try:
                                decimal_value = decimal.Decimal(next_value)
                                _, digits, exponent = decimal_value.as_tuple()
                                digits_after_dot = max(0, -exponent)
                                if digits_after_dot > max_digits_after_dot:
                                    max_digits_after_dot = digits_after_dot
                                digits_before_dot = len(digits) + exponent
                                if digits_before_dot > max_digits_before_dot:
                                    max_digits_before_dot = digits_before_dot
                            except decimal.DecimalException:
                                raise errors.InterfaceError(
                                    "number must be an decimal or integer but is: %s"
                                    % _compat.text_repr(next_value), location)
                            if after_hyphen:
                                decimal_value = decimal_value.copy_negate()
                                after_hyphen = False

                        if ellipsis_found:
                            if upper is None:
                                upper = decimal_value
                            else:
                                raise errors.InterfaceError(
                                    "range must have at most lower and upper limit but found another number: %s"
                                    % _compat.text_repr(next_value), location)
                        elif lower is None:
                            lower = decimal_value
                        else:
                            raise errors.InterfaceError(
                                "number must be followed by ellipsis (...) but found: %s"
                                % _compat.text_repr(next_value))
                    elif after_hyphen:
                        raise errors.InterfaceError(
                            "hyphen (-) must be followed by number but found: %s" % _compat.text_repr(next_value))
                    elif (next_type == token.OP) and (next_value == "-"):
                        after_hyphen = True
                    elif next_value in (ELLIPSIS, ':'):
                        ellipsis_found = True
                    else:
                        message = "range must be specified using decimal or integer numbers" \
                                  " and ellipsis (...) but found: %s [token type: %d]" \
                                  % (_compat.text_repr(next_value), next_type)
                        raise errors.InterfaceError(message)
                    next_token = next(tokens)

                if after_hyphen:
                    raise errors.InterfaceError("hyphen (-) at end must be followed by number")

                # Decide upon the result.
                if lower is None:
                    if upper is None:
                        if ellipsis_found:
                            # Handle "...".
                            # TODO: Handle "..." same as ""?
                            raise errors.InterfaceError("ellipsis (...) must be preceded and/or succeeded by number")

                    else:
                        assert ellipsis_found
                        # Handle "...y".
                        range_item = (None, upper)
                elif ellipsis_found:
                    # Handle "x..." and "x...y".
                    if (upper is not None) and (lower > upper):
                        raise errors.InterfaceError(
                            "lower limit %s must be less or equal than upper limit %s"
                            % (_decimal_as_text(lower, self.precision), _decimal_as_text(upper, self.precision)))
                    range_item = (lower, upper)
                else:
                    # Handle "x".
                    range_item = (lower, lower)
                if range_item is not None:
                    self._precision = max_digits_after_dot
                    self._scale = max_digits_before_dot + max_digits_after_dot
                    for item in self._items:
                        if self._items_overlap(item, range_item):
                            item_text = _compat.text_repr(self._repr_item(item))
                            result_text = _compat.text_repr(self._repr_item(range_item))
                            raise errors.InterfaceError(
                                "overlapping parts in decimal range must be cleaned up: %s and %s"
                                % (item_text, result_text), location)
                    self._items.append(range_item)
                if _tools.is_eof_token(next_token):
                    end_reached = True

            assert self.precision >= 0
            assert self.scale >= self.precision

            self._lower_limit = None
            self._upper_limit = None
            is_first_item = True
            for lower_item, upper_item in self._items:
                if is_first_item:
                    self._lower_limit = lower_item
                    self._upper_limit = upper_item
                    is_first_item = False

                if lower_item is None:
                    self._lower_limit = None
                elif (self._lower_limit is not None) and (lower_item < self._lower_limit):
                    self._lower_limit = lower_item

                if upper_item is None:
                    self._upper_limit = None
                elif (self._upper_limit is not None) and (upper_item > self._upper_limit):
                    self._upper_limit = upper_item
Exemplo n.º 13
0
    def __init__(self, description, default=None):
        """
        Setup a range as specified by ``description``.

        :param str description: a range description of the form \
          ``lower...upper`` or ``limit``. In case it is empty (``''``), any \
          value will be accepted by \
          :py:meth:`~cutplace.ranges.Range.validate()`. For example, \
          ``1...40`` accepts values between 1 and 40.
        :param str default: an alternative to use in case ``description`` is \
          ``None`` or empty.
        """
        assert default is None or (default.strip() != ''), "default=%r" % default

        # Find out if a `description` has been specified and if not, use optional `default` instead.
        has_description = (description is not None) and (description.strip() != '')
        if not has_description and default is not None:
            description = default
            has_description = True

        if not has_description:
            # Use empty ranges.
            self._description = None
            self._items = None
            self._lower_limit = None
            self._upper_limit = None
        else:
            self._description = description.replace('...', ELLIPSIS)
            self._items = []

            name_for_code = 'range'
            location = None  # TODO: Add location where range is declared.
            tokens = _tools.tokenize_without_space(self._description)
            end_reached = False
            while not end_reached:
                lower = None
                upper = None
                ellipsis_found = False
                after_hyphen = False
                next_token = next(tokens)
                while not _tools.is_eof_token(next_token) and not _tools.is_comma_token(next_token):
                    next_type = next_token[0]
                    next_value = next_token[1]
                    if next_type in (token.NAME, token.NUMBER, token.STRING):
                        if next_type == token.NAME:
                            # Symbolic names, e.g. ``tab``.
                            value_as_int = code_for_symbolic_token(name_for_code, next_value, location)
                        elif next_type == token.NUMBER:
                            # Numbers, e.g. ``123``.
                            value_as_int = code_for_number_token(name_for_code, next_value, location)
                            if after_hyphen:
                                value_as_int *= - 1
                                after_hyphen = False
                        elif next_type == token.STRING:
                            # Python strings, e.g. ``'abc'`` or ``"""abc"""``.
                            value_as_int = code_for_string_token(name_for_code, next_value, location)
                        elif (len(next_value) == 1) and not _tools.is_eof_token(next_token):
                            # Other single characters, e.g. ``,``; this is particular useful with delimiter properties.
                            value_as_int = ord(next_value)
                        else:
                            raise errors.InterfaceError(
                                'value for %s must a number, a single character or a symbolic name but is: %s'
                                % (name_for_code, _compat.text_repr(next_value)), location)
                        if ellipsis_found:
                            if upper is None:
                                upper = value_as_int
                            else:
                                raise errors.InterfaceError(
                                    "range must have at most lower and upper limit but found another number: %s"
                                    % _compat.text_repr(next_value), location)
                        elif lower is None:
                            lower = value_as_int
                        else:
                            raise errors.InterfaceError(
                                "number must be followed by ellipsis (...) but found: %s"
                                % _compat.text_repr(next_value), location)
                    elif after_hyphen:
                        raise errors.InterfaceError(
                            "hyphen (-) must be followed by number but found: %s" % _compat.text_repr(next_value),
                            location)
                    elif (next_type == token.OP) and (next_value == "-"):
                        after_hyphen = True
                    elif next_value in (ELLIPSIS, ':'):
                        ellipsis_found = True
                    else:
                        raise errors.InterfaceError(
                            "range must be specified using integer numbers, text, "
                            "symbols and ellipsis (...) but found: %s [token type: %d]"
                            % (_compat.text_repr(next_value), next_type), location)
                    next_token = next(tokens)

                if after_hyphen:
                    raise errors.InterfaceError("hyphen (-) at end must be followed by number", location)

                # Decide upon the result.
                if lower is None:
                    if upper is None:
                        if ellipsis_found:
                            # Handle "...".
                            raise errors.InterfaceError(
                                'ellipsis (...) must be preceded and/or succeeded by number', location)
                        else:
                            # Handle "".
                            result = None
                    else:
                        assert ellipsis_found
                        # Handle "...y".
                        result = (None, upper)
                elif ellipsis_found:
                    # Handle "x..." and "x...y".
                    if (upper is not None) and (lower > upper):
                        raise errors.InterfaceError(
                            "lower range %d must be greater or equal than upper range %d" % (lower, upper),
                            location)
                    result = (lower, upper)
                else:
                    # Handle "x".
                    result = (lower, lower)
                if result is not None:
                    for item in self._items:
                        if self._items_overlap(item, result):
                            item_text = _compat.text_repr(self._repr_item(item))
                            result_text = _compat.text_repr(self._repr_item(result))
                            raise errors.InterfaceError(
                                "overlapping parts in range must be cleaned up: %s and %s"
                                % (item_text, result_text), location)
                    self._items.append(result)
                if _tools.is_eof_token(next_token):
                    end_reached = True

            self._lower_limit = None
            self._upper_limit = None
            is_first_item = True
            for lower_item, upper_item in self._items:
                if is_first_item:
                    self._lower_limit = lower_item
                    self._upper_limit = upper_item
                    is_first_item = False

                if lower_item is None:
                    self._lower_limit = None
                elif (self._lower_limit is not None) and (lower_item < self._lower_limit):
                    self._lower_limit = lower_item

                if upper_item is None:
                    self._upper_limit = None
                elif (self._upper_limit is not None) and (upper_item > self._upper_limit):
                    self._upper_limit = upper_item