yaml.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. # Copyright (c) 2021, Open Source Robotics Foundation
  2. # All rights reserved.
  3. #
  4. # Redistribution and use in source and binary forms, with or without
  5. # modification, are permitted provided that the following conditions are met:
  6. #
  7. # * Redistributions of source code must retain the above copyright
  8. # notice, this list of conditions and the following disclaimer.
  9. # * Redistributions in binary form must reproduce the above copyright
  10. # notice, this list of conditions and the following disclaimer in the
  11. # documentation and/or other materials provided with the distribution.
  12. # * Neither the name of the Willow Garage, Inc. nor the names of its
  13. # contributors may be used to endorse or promote products derived from
  14. # this software without specific prior written permission.
  15. #
  16. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  17. # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  18. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  19. # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  20. # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  21. # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  22. # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  23. # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  24. # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  25. # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  26. # POSSIBILITY OF SUCH DAMAGE.
  27. import yaml
  28. class AnnotatedSafeLoader(yaml.SafeLoader):
  29. """
  30. YAML loader that adds '__line__' attributes to some of the parsed data.
  31. This extension of the PyYAML SafeLoader replaces some basic types with
  32. derived types that include a '__line__' attribute to determine where
  33. the deserialized data can be found in the YAML file it was parsed from.
  34. """
  35. class AnnotatedDict(dict):
  36. __slots__ = ('__line__',)
  37. def __init__(self, *args, **kwargs):
  38. return super().__init__(*args, **kwargs)
  39. class AnnotatedList(list):
  40. __slots__ = ('__line__',)
  41. def __init__(self, *args, **kwargs):
  42. return super().__init__(*args, **kwargs)
  43. class AnnotatedStr(str):
  44. __slots__ = ('__line__',)
  45. def __new__(cls, *args, **kwargs):
  46. return str.__new__(cls, *args, **kwargs)
  47. def compose_node(self, parent, index):
  48. line = self.line
  49. node = super().compose_node(parent, index)
  50. node.__line__ = line + 1
  51. return node
  52. def construct_annotated_map(self, node):
  53. data = AnnotatedSafeLoader.AnnotatedDict()
  54. data.__line__ = node.__line__
  55. yield data
  56. value = self.construct_mapping(node)
  57. data.update(value)
  58. def construct_annotated_seq(self, node):
  59. data = AnnotatedSafeLoader.AnnotatedList()
  60. data.__line__ = node.__line__
  61. yield data
  62. data.extend(self.construct_sequence(node))
  63. def construct_annotated_str(self, node):
  64. data = self.construct_yaml_str(node)
  65. data = AnnotatedSafeLoader.AnnotatedStr(data)
  66. data.__line__ = node.__line__
  67. return data
  68. AnnotatedSafeLoader.add_constructor(
  69. 'tag:yaml.org,2002:map', AnnotatedSafeLoader.construct_annotated_map)
  70. AnnotatedSafeLoader.add_constructor(
  71. 'tag:yaml.org,2002:seq', AnnotatedSafeLoader.construct_annotated_seq)
  72. AnnotatedSafeLoader.add_constructor(
  73. 'tag:yaml.org,2002:str', AnnotatedSafeLoader.construct_annotated_str)
  74. def merge_dict(base, to_add):
  75. """Merge two mappings, overwriting the first mapping with data from the second."""
  76. for k, v in to_add.items():
  77. if isinstance(v, dict) and isinstance(base.get(k), dict):
  78. merge_dict(base[k], v)
  79. else:
  80. base[k] = v
  81. def isolate_yaml_snippets_from_line_numbers(yaml_dict, line_numbers):
  82. """
  83. Create a mapping that contains data parsed from particular lines of the source file.
  84. This function preserves the ancestry of a nested mapping even if those lines are not
  85. specifically requested.
  86. :param yaml_dict: a mapping parsed using the AnnotatedSafeLoader.
  87. :param line_numbers: a collection of line numbers to include in the isolated snippets.
  88. :returns: a subset of the original data based on the given line numbers.
  89. """
  90. matches = {}
  91. for dl in line_numbers:
  92. for name, values in reversed(yaml_dict.items()):
  93. if isinstance(values, AnnotatedSafeLoader.AnnotatedDict):
  94. if values.__line__ <= dl:
  95. merge_dict(matches,
  96. {name: isolate_yaml_snippets_from_line_numbers(values, [dl])})
  97. break
  98. elif isinstance(values, AnnotatedSafeLoader.AnnotatedList):
  99. if values.__line__ <= dl:
  100. matches[name] = values
  101. break
  102. return matches