From 784b4a6ad8a08b3f5a1ee653b80f13e1c3a30075 Mon Sep 17 00:00:00 2001 From: Anthon van der Neut Date: Fri, 14 Apr 2017 08:48:38 +0200 Subject: fix issue 112: hexadecimal not preserved please **close** this issue if fixed --- CHANGES | 8 ++++++ README.rst | 9 +++++-- __init__.py | 4 +-- _test/test_fail.py | 4 +-- _test/test_int.py | 43 +++++++++++++++++++++++++++++++ constructor.py | 40 +++++++++++++++++++++++++++++ representer.py | 36 +++++++++++++++++++++++++- scalarint.py | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ scalarstring.py | 3 +-- 9 files changed, 213 insertions(+), 9 deletions(-) create mode 100644 _test/test_int.py create mode 100644 scalarint.py diff --git a/CHANGES b/CHANGES index 6f116a9..063576e 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,11 @@ +[0, 14, 6]: 2017-04-14 + - binary, octal and hex integers are now preserved by default. This + was a known deficiency. Working on this was prompted by the issue report (112) + from devnoname120, as well as the additional experience with `.replace()` + on `scalarstring` classes. + - fix issues 114: cannot install on Buildozer (reported by mixmastamyk). + Setting env. var ``RUAMEL_NO_PIP_INSTALL_CHECK`` will suppress ``pip``-check. + [0, 14, 5]: 2017-04-04 - fix issue 109: None not dumping correctly at top level (reported by Andrea Censi) - fix issue 110: .replace on Preserved/DoubleQuoted/SingleQuoted ScalarString diff --git a/README.rst b/README.rst index ce30899..ee43342 100644 --- a/README.rst +++ b/README.rst @@ -18,7 +18,11 @@ ChangeLog .. should insert NEXT: at the beginning of line for next key -NEXT: +0.14.6 (2017-04-14): + - binary, octal and hex integers are now preserved by default. This + was a known deficiency. Working on this was prompted by the issue report (112) + from devnoname120, as well as the additional experience with `.replace()` + on `scalarstring` classes. - fix issues 114: cannot install on Buildozer (reported by mixmastamyk). Setting env. var ``RUAMEL_NO_PIP_INSTALL_CHECK`` will suppress ``pip``-check. @@ -112,6 +116,7 @@ NEXT: `StackOveflow Q&A `_ by `msinn `_) ---- +---- + For older changes see the file `CHANGES `_ diff --git a/__init__.py b/__init__.py index a7976a3..7963ae0 100644 --- a/__init__.py +++ b/__init__.py @@ -10,8 +10,8 @@ from typing import Dict, Any # NOQA _package_data = dict( full_package_name='ruamel.yaml', - version_info=(0, 14, 6, 'dev'), - __version__='0.14.6.dev', + version_info=(0, 14, 6), + __version__='0.14.6', author='Anthon van der Neut', author_email='a.van.der.neut@ruamel.eu', description='ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order', # NOQA diff --git a/_test/test_fail.py b/_test/test_fail.py index 300bfa6..61b8ffb 100644 --- a/_test/test_fail.py +++ b/_test/test_fail.py @@ -220,8 +220,8 @@ class TestFlowValues: {a: bcd:efg} """) - @pytest.mark.xfail(strict=True) + # @pytest.mark.xfail(strict=True) def test_flow_value_with_colon_quoted(self): round_trip("""\ {a: 'bcd:efg'} - """) + """, preserve_quotes=True) diff --git a/_test/test_int.py b/_test/test_int.py new file mode 100644 index 0000000..6776b51 --- /dev/null +++ b/_test/test_int.py @@ -0,0 +1,43 @@ +# coding: utf-8 + +from __future__ import print_function, absolute_import, division, unicode_literals + +import pytest # NOQA + +from roundtrip import round_trip, dedent, round_trip_load, round_trip_dump + + +class TestBinHexOct: + # @pytest.mark.xfail(strict=True) + def test_round_trip_hex_oct(self): + round_trip("""\ + - 42 + - 0b101010 + - 0x2a + - 0x2A + - 0o52 + """) + + def test_calculate(self): + s = dedent("""\ + - 42 + - 0b101010 + - 0x2a + - 0x2A + - 0o52 + """) + x = round_trip_load(s) + for idx, elem in enumerate(x): + # x[idx] = type(elem)(elem - 21) + elem -= 21 + x[idx] = elem + for idx, elem in enumerate(x): + # x[idx] = type(elem)(2 * elem) + elem *= 2 + x[idx] = elem + for idx, elem in enumerate(x): + t = elem + elem **= 2 + elem //= t + x[idx] = elem + assert round_trip_dump(x) == s diff --git a/constructor.py b/constructor.py index 6797d95..f7baef1 100644 --- a/constructor.py +++ b/constructor.py @@ -24,6 +24,7 @@ from ruamel.yaml.comments import (CommentedMap, CommentedOrderedMap, CommentedSe from ruamel.yaml.scalarstring import * # NOQA from ruamel.yaml.scalarstring import (PreservedScalarString, SingleQuotedScalarString, DoubleQuotedScalarString, ScalarString) +from ruamel.yaml.scalarint import BinaryInt, OctalInt, HexInt, HexCapsInt from ruamel.yaml.timestamp import TimeStamp __all__ = ['BaseConstructor', 'SafeConstructor', 'Constructor', @@ -907,6 +908,45 @@ class RoundTripConstructor(SafeConstructor): return DoubleQuotedScalarString(node.value) return node.value + def construct_yaml_int(self, node): + # type: (Any) -> Any + value_s = to_str(self.construct_scalar(node)) + value_s = value_s.replace('_', '') + sign = +1 + if value_s[0] == '-': + sign = -1 + if value_s[0] in '+-': + value_s = value_s[1:] + if value_s == '0': + return 0 + elif value_s.startswith('0b'): + return BinaryInt(sign*int(value_s[2:], 2)) + elif value_s.startswith('0x'): + # default to lower-case if no a-fA-F in string + hex_fun = HexInt # type: Any + for ch in value_s[2:]: + if ch in 'ABCDEF': # first non-digit is capital + hex_fun = HexCapsInt + break + if ch in 'abcdef': + break + return hex_fun(sign*int(value_s[2:], 16)) + elif value_s.startswith('0o'): + return OctalInt(sign*int(value_s[2:], 8)) + elif self.resolver.processing_version != (1, 2) and value_s[0] == '0': + return sign*int(value_s, 8) + elif self.resolver.processing_version != (1, 2) and ':' in value_s: + digits = [int(part) for part in value_s.split(':')] + digits.reverse() + base = 1 + value = 0 + for digit in digits: + value += digit*base + base *= 60 + return sign*value + else: + return sign*int(value_s) + def construct_yaml_str(self, node): # type: (Any) -> Any value = self.construct_scalar(node) diff --git a/representer.py b/representer.py index 5db1d98..0c19a61 100644 --- a/representer.py +++ b/representer.py @@ -8,7 +8,9 @@ from typing import Dict, List, Any, Union # NOQA from ruamel.yaml.error import * # NOQA from ruamel.yaml.nodes import * # NOQA from ruamel.yaml.compat import text_type, binary_type, to_unicode, PY2, PY3, ordereddict -from ruamel.yaml.scalarstring import * # NOQA +from ruamel.yaml.scalarstring import (PreservedScalarString, SingleQuotedScalarString, + DoubleQuotedScalarString) +from ruamel.yaml.scalarint import BinaryInt, OctalInt, HexInt, HexCapsInt from ruamel.yaml.timestamp import TimeStamp import datetime @@ -676,6 +678,22 @@ class RoundTripRepresenter(SafeRepresenter): tag = u'tag:yaml.org,2002:str' return self.represent_scalar(tag, data, style=style) + def represent_binary_int(self, data): + # type: (Any) -> Any + return self.represent_scalar(u'tag:yaml.org,2002:int', '0b' + format(data, 'b')) + + def represent_octal_int(self, data): + # type: (Any) -> Any + return self.represent_scalar(u'tag:yaml.org,2002:int', '0o' + format(data, 'o')) + + def represent_hex_int(self, data): + # type: (Any) -> Any + return self.represent_scalar(u'tag:yaml.org,2002:int', '0x' + format(data, 'x')) + + def represent_hex_caps_int(self, data): + # type: (Any) -> Any + return self.represent_scalar(u'tag:yaml.org,2002:int', '0x' + format(data, 'X')) + def represent_sequence(self, tag, sequence, flow_style=None): # type: (Any, Any, Any) -> Any value = [] # type: List[Any] @@ -952,6 +970,22 @@ RoundTripRepresenter.add_representer( DoubleQuotedScalarString, RoundTripRepresenter.represent_double_quoted_scalarstring) +RoundTripRepresenter.add_representer( + BinaryInt, + RoundTripRepresenter.represent_binary_int) + +RoundTripRepresenter.add_representer( + OctalInt, + RoundTripRepresenter.represent_octal_int) + +RoundTripRepresenter.add_representer( + HexInt, + RoundTripRepresenter.represent_hex_int) + +RoundTripRepresenter.add_representer( + HexCapsInt, + RoundTripRepresenter.represent_hex_caps_int) + RoundTripRepresenter.add_representer(CommentedSeq, RoundTripRepresenter.represent_list) diff --git a/scalarint.py b/scalarint.py new file mode 100644 index 0000000..e2028e3 --- /dev/null +++ b/scalarint.py @@ -0,0 +1,75 @@ +# coding: utf-8 + +from __future__ import print_function, absolute_import, division, unicode_literals + +import sys + +if sys.version_info >= (3, 5, 2): + from typing import Text, Any, Dict, List # NOQA + +__all__ = ["ScalarInt", "BinaryInt", "OctalInt", "HexInt", "HexCapsInt"] + + +class ScalarInt(int): + __slots__ = () + + def __new__(cls, *args, **kw): + # type: (Any, Any) -> Any + return int.__new__(cls, *args, **kw) # type: ignore + + def __iadd__(self, a): # type: ignore + # type: (Any) -> Any + return type(self)(self + a) + + def __ifloordiv__(self, a): # type: ignore + # type: (Any) -> Any + return type(self)(self // a) + + def __imul__(self, a): # type: ignore + # type: (Any) -> Any + return type(self)(self * a) + + def __ipow__(self, a): # type: ignore + # type: (Any) -> Any + return type(self)(self ** a) + + def __isub__(self, a): # type: ignore + # type: (Any) -> Any + return type(self)(self - a) + + +class BinaryInt(ScalarInt): + __slots__ = () + + def __new__(cls, value): + # type: (Text) -> Any + return ScalarInt.__new__(cls, value) + + +class OctalInt(ScalarInt): + __slots__ = () + + def __new__(cls, value): + # type: (Text) -> Any + return ScalarInt.__new__(cls, value) + + +# mixed casing of A-F is not supported, when loading the first non digit +# determines the case + +class HexInt(ScalarInt): + """uses lower case (a-f)""" + __slots__ = () + + def __new__(cls, value): + # type: (Text) -> Any + return ScalarInt.__new__(cls, value) + + +class HexCapsInt(ScalarInt): + """uses upper case (A-F)""" + __slots__ = () + + def __new__(cls, value): + # type: (Text) -> Any + return ScalarInt.__new__(cls, value) diff --git a/scalarstring.py b/scalarstring.py index 3282c1e..05e4593 100644 --- a/scalarstring.py +++ b/scalarstring.py @@ -1,7 +1,6 @@ # coding: utf-8 -from __future__ import absolute_import -from __future__ import print_function +from __future__ import print_function, absolute_import, division, unicode_literals import sys -- cgit v1.2.1