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()]