| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| from copy import deepcopy |
| |
| |
| from ...utils.console import puts, Colored, indent |
| |
| |
| # We are inheriting the primitive types in order to add the ability to set |
| # an attribute (_locator) on them. |
| |
| class LocatableString(unicode): |
| pass |
| |
| |
| class LocatableInt(int): |
| pass |
| |
| |
| class LocatableFloat(float): |
| pass |
| |
| |
| def wrap(value): |
| if isinstance(value, basestring): |
| return True, LocatableString(value) |
| elif isinstance(value, int) and \ |
| not isinstance(value, bool): # Note: bool counts as int in Python! |
| return True, LocatableInt(value) |
| elif isinstance(value, float): |
| return True, LocatableFloat(value) |
| return False, value |
| |
| |
| class Locator(object): |
| """ |
| Stores location information (line and column numbers) for agnostic raw data. |
| """ |
| def __init__(self, location, line, column, children=None): |
| self.location = location |
| self.line = line |
| self.column = column |
| self.children = children |
| |
| def get_child(self, *names): |
| if (not names) or (not isinstance(self.children, dict)): |
| return self |
| name = names[0] |
| if name not in self.children: |
| return self |
| child = self.children[name] |
| return child.get_child(names[1:]) |
| |
| def link(self, raw, path=None): |
| if hasattr(raw, '_locator'): |
| # This can happen when we use anchors |
| return |
| |
| try: |
| setattr(raw, '_locator', self) |
| except AttributeError: |
| return |
| |
| if isinstance(raw, list): |
| for i, raw_element in enumerate(raw): |
| wrapped, raw_element = wrap(raw_element) |
| if wrapped: |
| raw[i] = raw_element |
| child_path = '%s.%d' % (path, i) if path else str(i) |
| try: |
| self.children[i].link(raw_element, child_path) |
| except KeyError: |
| raise ValueError('location map does not match agnostic raw data: %s' % |
| child_path) |
| elif isinstance(raw, dict): |
| for k, raw_element in raw.iteritems(): |
| wrapped, raw_element = wrap(raw_element) |
| if wrapped: |
| raw[k] = raw_element |
| child_path = '%s.%s' % (path, k) if path else k |
| try: |
| self.children[k].link(raw_element, child_path) |
| except KeyError: |
| raise ValueError('location map does not match agnostic raw data: %s' % |
| child_path) |
| |
| def merge(self, locator): |
| if isinstance(self.children, dict) and isinstance(locator.children, dict): |
| for k, loc in locator.children.iteritems(): |
| if k in self.children: |
| self.children[k].merge(loc) |
| else: |
| self.children[k] = loc |
| |
| def dump(self, key=None): |
| if key: |
| puts('%s "%s":%d:%d' % |
| (Colored.red(key), Colored.blue(self.location), self.line, self.column)) |
| else: |
| puts('"%s":%d:%d' % (Colored.blue(self.location), self.line, self.column)) |
| if isinstance(self.children, list): |
| with indent(2): |
| for loc in self.children: |
| loc.dump() |
| elif isinstance(self.children, dict): |
| with indent(2): |
| for k, loc in self.children.iteritems(): |
| loc.dump(k) |
| |
| def __str__(self): |
| # Should be in same format as Issue.locator_as_str |
| return '"%s":%d:%d' % (self.location, self.line, self.column) |
| |
| |
| def deepcopy_with_locators(value): |
| """ |
| Like :func:`deepcopy`, but also copies over locators. |
| """ |
| |
| res = deepcopy(value) |
| copy_locators(res, value) |
| return res |
| |
| |
| def copy_locators(target, source): |
| """ |
| Copies over ``_locator`` for all elements, recursively. |
| |
| Assumes that target and source have exactly the same list/dict structure. |
| """ |
| |
| locator = getattr(source, '_locator', None) |
| if locator is not None: |
| try: |
| setattr(target, '_locator', locator) |
| except AttributeError: |
| pass |
| |
| if isinstance(target, list) and isinstance(source, list): |
| for i, _ in enumerate(target): |
| copy_locators(target[i], source[i]) |
| elif isinstance(target, dict) and isinstance(source, dict): |
| for k, v in target.items(): |
| copy_locators(v, source[k]) |