0

I followed your advice in this post to get line numbers for strings. Worked like a charm. It was then suggested a similar pattern can be used to get line numbers for bools and ints. So I tried a few things and finally got it to work for bools, but I had to edit 1 line in the ruamel.yaml source code to get it to work. My question is, is this the proper way to implement line numbers for bools? Or is there a way to do it without having to modifying the ruamel source code?

Here is the one line in ruamel I had to change in constructor.py: Do not do the conversion/lookup back to a native bool, but instead return the overloaded ScalarBoolean which holds the line number:

def construct_yaml_bool(self, node):
        # type: (Any) -> bool
        value = self.construct_scalar(node)
        return value # add this
        # return self.bool_values[value.lower()]. # remove this 

And the implementation I did to overload the constructor and process booleans (I can probably get rid of the lower() class method now that I'm not calling lower() from the ruamel source):

 import ruamel.yaml


class Boolean(ruamel.yaml.scalarbool.ScalarBoolean):
    def __init__(self, value):  # constructor
        self.__slots__ = ["lc", "bool_values", "internal_value"]
        self.bool_values = []
        if value is True:
            self.bool_values.append("true")
            self.internal_value = True
        else:
            self.bool_values.append("false")
            self.internal_value = False

    def lower(self):
        if self.internal_value is True:
            return "true"
        else:
            return "false"


class BoolStr(ruamel.yaml.scalarstring.ScalarString):
    __slots__ = "lc"

    style = ""

    def __new__(cls, value):
        return ruamel.yaml.scalarstring.ScalarString.__new__(cls, value)

    # def __init__(self, value):  # constructor
    #     self.__slots__ = "lc"
    # self.bool_values = []

    #     if value is True:
    #         # self.bool_values.append("true")
    #         self.internal_value = True
    #     else:
    #         # self.bool_values.append("false")
    #         self.internal_value = False

    # def lower(self):
    #     if self.internal_value is True:
    #         return "true"
    #     else:
    #         return "false"


class Str(ruamel.yaml.scalarstring.ScalarString):
    __slots__ = "lc"

    style = ""

    def __new__(cls, value):
        return ruamel.yaml.scalarstring.ScalarString.__new__(cls, value)


class MyPreservedScalarString(ruamel.yaml.scalarstring.PreservedScalarString):
    __slots__ = "lc"


class MyDoubleQuotedScalarString(ruamel.yaml.scalarstring.DoubleQuotedScalarString):
    __slots__ = "lc"


class MySingleQuotedScalarString(ruamel.yaml.scalarstring.SingleQuotedScalarString):
    __slots__ = "lc"


class MyConstructor(ruamel.yaml.constructor.RoundTripConstructor):
    def construct_scalar(self, node):
        # type: (Any) -> Any
        if not isinstance(node, ruamel.yaml.nodes.ScalarNode):
            raise ruamel.yaml.constructor.ConstructorError(
                None, None, "expected a scalar node, but found %s" % node.id, node.start_mark
            )

        if node.style == "|" and isinstance(node.value, ruamel.yaml.compat.text_type):
            ret_val = MyPreservedScalarString(node.value)
        elif bool(self._preserve_quotes) and isinstance(node.value, ruamel.yaml.compat.text_type):
            if node.style == "'":
                ret_val = MySingleQuotedScalarString(node.value)
            elif node.style == '"':
                ret_val = MyDoubleQuotedScalarString(node.value)
            elif node.style is None and (node.value == "true" or node.value == "false"):
                if node.value == "true":
                    # ret_val = BoolStr(node.value)
                    ret_val = Boolean(True)
                else:
                    # ret_val = BoolStr(node.value)
                    ret_val = Boolean(False)
            else:
                ret_val = Str(node.value)

        else:
            ret_val = Str(node.value)
        ret_val.lc = ruamel.yaml.comments.LineCol()
        ret_val.lc.line = node.start_mark.line + 1
        ret_val.lc.col = node.start_mark.column
        return ret_val

Any advice is greatly appreciated.

Edit: I de-monkey-patched your code and put the overload in MyConstructor, but contruct_yaml_bool never gets called inside of MyConstructor. construct_scalar gets called, but never construct_yaml_bool.

Here is the code I use to setup ruamel.yaml:

yaml = YAML()
    yaml.preserve_quotes = True
    yaml.Constructor = yaml_parser_classes.MyConstructor
    with open(full_path_to_file) as fp:
        yaml_data = yaml.load(fp)

And here is my full file to overload the round trip constructor:

import ruamel.yaml


class Boolean(ruamel.yaml.scalarbool.ScalarBoolean):
    def __init__(self, value):  # constructor
        self.__slots__ = ["lc", "bool_values", "internal_value"]
        self.bool_values = []
        if value is True:
            self.bool_values.append("true")
            self.internal_value = True
        else:
            self.bool_values.append("false")
            self.internal_value = False


class BoolStr(ruamel.yaml.scalarstring.ScalarString):
    __slots__ = "lc"

    style = ""

    def __new__(cls, value):
        return ruamel.yaml.scalarstring.ScalarString.__new__(cls, value)


class Str(ruamel.yaml.scalarstring.ScalarString):
    __slots__ = "lc"

    style = ""

    def __new__(cls, value):
        return ruamel.yaml.scalarstring.ScalarString.__new__(cls, value)


class MyPreservedScalarString(ruamel.yaml.scalarstring.PreservedScalarString):
    __slots__ = "lc"


class MyDoubleQuotedScalarString(ruamel.yaml.scalarstring.DoubleQuotedScalarString):
    __slots__ = "lc"


class MySingleQuotedScalarString(ruamel.yaml.scalarstring.SingleQuotedScalarString):
    __slots__ = "lc"


class MyConstructor(ruamel.yaml.constructor.RoundTripConstructor):
    def construct_scalar(self, node):
        # type: (Any) -> Any
        if not isinstance(node, ruamel.yaml.nodes.ScalarNode):
            raise ruamel.yaml.constructor.ConstructorError(
                None, None, "expected a scalar node, but found %s" % node.id, node.start_mark
            )

        if node.style == "|" and isinstance(node.value, ruamel.yaml.compat.text_type):
            ret_val = MyPreservedScalarString(node.value)
        elif bool(self._preserve_quotes) and isinstance(node.value, ruamel.yaml.compat.text_type):
            if node.style == "'":
                ret_val = MySingleQuotedScalarString(node.value)
            elif node.style == '"':
                ret_val = MyDoubleQuotedScalarString(node.value)
            elif node.style is None and (node.value == "true" or node.value == "false"):
                if node.value == "true":
                    # ret_val = BoolStr(node.value)
                    ret_val = Boolean(True)
                else:
                    # ret_val = BoolStr(node.value)
                    ret_val = Boolean(False)
            else:
                ret_val = Str(node.value)

        else:
            ret_val = Str(node.value)
        ret_val.lc = ruamel.yaml.comments.LineCol()
        ret_val.lc.line = node.start_mark.line + 1
        ret_val.lc.col = node.start_mark.column
        return ret_val

    def construct_yaml_bool(self, node):
        # type: (Any) -> bool
        value = self.construct_scalar(node)
        return value  # Changed to this to get bool with line numbers working
        # Replace below lookup of raw bool type with above
        # return which returns our ScalarBoolean with line numbers
        # return self.bool_values[value.lower()]
LuddyLow
  • 1
  • 1
  • You don't need to change the source of ruamel.yaml. You can either monkey-patch your `construct_yaml_bool()` on the `SafeConstructor` or insert the code of it in a copy of `RoundTripConstrustor.construct_yaml_bool` you provide as a method to `MyConstructor`. (You should have provided a complete minimal example, ie. working code, not just snippets). – Anthon Nov 06 '20 at 06:28
  • Thank you for your response. I see what you mean providing a new construct_yaml_bool method to MyConstructor. So I put your source code back to original and added the above method to MyConstructor (See Edit above in Original post for the new source code). Problem is, my construct_yaml_bool never gets called. Any thoughts? – LuddyLow Nov 09 '20 at 22:01

0 Answers0