1 # Licensed to the Apache Software Foundation (ASF) under one or more
2 # contributor license agreements. See the NOTICE file distributed with
3 # this work for additional information regarding copyright ownership.
4 # The ASF licenses this file to You under the Apache License, Version 2.0
5 # (the "License"); you may not use this file except in compliance with
6 # the License. You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
16 from __future__ import absolute_import # so we can import standard 'collections'
18 from ...utils import (
31 Platform error (e.g. I/O, hardware, a bug in ARIA)
36 Syntax and format (e.g. YAML, XML, JSON)
46 Relationships between fields within the type (internal grammar)
51 Relationships between types (e.g. inheritance, external grammar)
56 Topology (e.g. static requirements and capabilities)
61 External (e.g. live requirements and capabilities)
66 def __init__(self, message=None, exception=None, location=None, line=None,
67 column=None, locator=None, snippet=None, level=0):
68 if message is not None:
69 self.message = str(message)
70 elif exception is not None:
71 self.message = str(exception)
73 self.message = 'unknown issue'
75 self.exception = exception
77 if locator is not None:
78 self.location = locator.location
79 self.line = locator.line
80 self.column = locator.column
82 self.location = location
86 self.snippet = snippet
91 return collections.OrderedDict((
92 ('level', self.level),
93 ('message', self.message),
94 ('location', self.location),
96 ('column', self.column),
97 ('snippet', self.snippet),
98 ('exception', type.full_type_name(self.exception) if self.exception else None)))
101 def locator_as_str(self):
102 if self.location is not None:
103 if self.line is not None:
104 if self.column is not None:
105 return '"%s":%d:%d' % (self.location, self.line, self.column)
107 return '"%s":%d' % (self.location, self.line)
109 return '"%s"' % self.location
114 def heading_as_str(self):
115 return '%d: %s' % (self.level, self.message)
118 def details_as_str(self):
120 locator = self.locator_as_str
121 if locator is not None:
122 details_str += '@%s' % locator
123 if self.snippet is not None:
124 details_str += '\n%s' % self.snippet
128 heading_str = self.heading_as_str
129 details = self.details_as_str
131 heading_str += ', ' + details
135 class ReporterMixin(object):
139 def __init__(self, *args, **kwargs):
140 super(ReporterMixin, self).__init__(*args, **kwargs)
141 self._issues = threading.LockedList()
142 self.max_level = self.Issue.ALL
144 def report(self, message=None, exception=None, location=None, line=None,
145 column=None, locator=None, snippet=None, level=Issue.PLATFORM, issue=None):
147 issue = self.Issue(message, exception, location, line, column, locator, snippet, level)
149 # Avoid duplicate issues
151 for i in self._issues:
152 if str(i) == str(issue):
155 self._issues.append(issue)
158 def has_issues(self):
159 return len(self._issues) > 0
163 issues = [i for i in self._issues if i.level <= self.max_level]
164 issues.sort(key=lambda i: (i.level, i.location, i.line, i.column, i.message))
165 return collections.FrozenList(issues)
168 def issues_as_raw(self):
169 return [formatting.as_raw(i) for i in self.issues]
171 def extend_issues(self, *issues):
173 self._issues.extend(*issues)
175 def dump_issues(self):
178 console.puts(console.Colored.blue('Validation issues:', bold=True))
179 with console.indent(2):
181 console.puts(console.Colored.blue(issue.heading_as_str))
182 details = issue.details_as_str
184 with console.indent(3):
185 console.puts(details)
186 if issue.exception is not None:
187 with console.indent(3):
188 exceptions.print_exception(issue.exception)