Better reporting syntax error positions in config files

This commit is contained in:
Maxim Kulkin
2013-09-17 16:56:54 +04:00
parent b7d0568127
commit 2a6fc21e24
4 changed files with 42 additions and 37 deletions

View File

@@ -7,12 +7,15 @@ from ostack_validator.config_formats.common import *
class IniConfigParser: class IniConfigParser:
key_value_re = re.compile("^(\w+)\s*([:=])\s*('.*'|\".*\"|.*)\s*$") key_value_re = re.compile("^(\w+)\s*([:=])\s*('.*'|\".*\"|.*)\s*$")
def parse(self, name, io): def parse(self, name, base_mark, io):
if not hasattr(io, 'readlines'): if not hasattr(io, 'readlines'):
io = StringIO(io) io = StringIO(io)
def mark(line, column=0):
return base_mark.merge(Mark('', line, column))
errors = [] errors = []
current_section_name = ConfigSectionName(Mark(name, 0, 0), Mark(name, 0, 0), '') current_section_name = ConfigSectionName(mark(0), mark(0), '')
current_param_name = None current_param_name = None
current_param_value = None current_param_value = None
current_param_delimiter = None current_param_delimiter = None
@@ -37,52 +40,52 @@ class IniConfigParser:
if line[0].isspace(): if line[0].isspace():
if current_param_name: if current_param_name:
current_param_value.end_mark = Mark(name, line_number, len(line)) current_param_value.end_mark = mark(line_number, len(line))
current_param_value.text += line.lstrip() current_param_value.text += line.lstrip()
continue continue
else: else:
errors.append(ParseError('Unexpected multiline value continuation', Mark(name, line_number, 0))) errors.append(ParseError('Unexpected multiline value continuation', mark(line_number)))
continue continue
if line[0] == '[': if line[0] == '[':
end_index = line.find(']') end_index = line.find(']')
if end_index == -1: if end_index == -1:
errors.append(ParseError('Unclosed section', Mark(name, line_number, len(line)))) errors.append(ParseError('Unclosed section', mark(line_number, len(line))))
end_index = len(line) end_index = len(line)
while line[end_index-1].isspace(): end_index -= 1 while line[end_index-1].isspace(): end_index -= 1
if end_index <= 1: if end_index <= 1:
errors.append(ParseError('Missing section name', Mark(name, line_number, 0))) errors.append(ParseError('Missing section name', mark(line_number)))
continue continue
else: else:
i = end_index+1 i = end_index+1
while i < len(line): while i < len(line):
if not line[i].isspace(): if not line[i].isspace():
errors.append(ParseError('Extra chars after section name', Mark(name, line_number, i))) errors.append(ParseError('Extra chars after section name', mark(line_number, i)))
break break
i += 1 i += 1
if current_section_name.text != '' or len(parameters) > 0: if current_section_name.text != '' or len(parameters) > 0:
section = ConfigSection(current_section_name.start_mark, Mark(name, line_number, 0), current_section_name, parameters) section = ConfigSection(current_section_name.start_mark, mark(line_number), current_section_name, parameters)
sections.append(section) sections.append(section)
parameters = [] parameters = []
current_section_name = ConfigSectionName( current_section_name = ConfigSectionName(
Mark(name, line_number, 0), mark(line_number, 0),
Mark(name, line_number, end_index), mark(line_number, end_index),
line[1:end_index] line[1:end_index]
) )
else: else:
m = self.key_value_re.match(line) m = self.key_value_re.match(line)
if m: if m:
current_param_name = ConfigParameterName( current_param_name = ConfigParameterName(
Mark(name, line_number, m.start(1)), mark(line_number, m.start(1)),
Mark(name, line_number, m.end(1)), mark(line_number, m.end(1)),
m.group(1) m.group(1)
) )
current_param_delimiter = TextElement( current_param_delimiter = TextElement(
Mark(name, line_number, m.start(2)), mark(line_number, m.start(2)),
Mark(name, line_number, m.end(2)), mark(line_number, m.end(2)),
m.group(2) m.group(2)
) )
@@ -94,13 +97,13 @@ class IniConfigParser:
value = value[1:-1] value = value[1:-1]
current_param_value = ConfigParameterValue( current_param_value = ConfigParameterValue(
Mark(name, line_number, m.start(3)), mark(line_number, m.start(3)),
Mark(name, line_number, m.end(3)), mark(line_number, m.end(3)),
value, value,
quotechar quotechar
) )
else: else:
errors.append(ParseError('Syntax error', Mark(name, line_number, 0))) errors.append(ParseError('Syntax error', mark(line_number)))
line_number += 1 line_number += 1
@@ -109,11 +112,15 @@ class IniConfigParser:
parameters.append(param) parameters.append(param)
if current_section_name.text != '' or len(parameters) > 0: if current_section_name.text != '' or len(parameters) > 0:
section = ConfigSection(current_section_name.start_mark, Mark(name, line_number, 0), current_section_name, parameters) section = ConfigSection(current_section_name.start_mark, mark(line_number), current_section_name, parameters)
sections.append(section) sections.append(section)
parameters = [] parameters = []
config = ComponentConfig(name, Mark(name), sections, errors) end_mark = base_mark
if len(sections) > 0:
end_mark = base_mark.merge(sections[-1].end_mark)
config = ComponentConfig(base_mark, end_mark, name, sections, errors)
return config return config

View File

@@ -22,7 +22,7 @@ class IniConfigParserTests(unittest.TestCase):
if margin: if margin:
content = self._strip_margin(content) content = self._strip_margin(content)
return self.parser.parse('test.conf', content) return self.parser.parse('test.conf', Mark(''), content)
def test_parsing(self): def test_parsing(self):
config = self.parse("param1 = value1") config = self.parse("param1 = value1")

View File

@@ -41,11 +41,11 @@ class MainConfigValidationInspection(Inspection):
for parameter in section.parameters: for parameter in section.parameters:
parameter_schema = schema.get_parameter(name=parameter.name.text, section=section.name.text) parameter_schema = schema.get_parameter(name=parameter.name.text, section=section.name.text)
if not parameter_schema: if not parameter_schema:
results.append(MarkedIssue(Issue.WARNING, 'Unknown parameter "%s"' % parameter.name.text, main_config.mark.merge(parameter.start_mark))) results.append(MarkedIssue(Issue.WARNING, 'Unknown parameter "%s"' % parameter.name.text, parameter.start_mark))
continue continue
if parameter.name.text in seen_parameters: if parameter.name.text in seen_parameters:
results.append(MarkedIssue(Issue.WARNING, 'Parameter "%s" in section "%s" redeclared' % (parameter.name.text, section_name), main_config.mark.merge(parameter.start_mark))) results.append(MarkedIssue(Issue.WARNING, 'Parameter "%s" in section "%s" redeclared' % (parameter.name.text, section_name), parameter.start_mark))
else: else:
seen_parameters.add(parameter.name.text) seen_parameters.add(parameter.name.text)
@@ -53,13 +53,13 @@ class MainConfigValidationInspection(Inspection):
type_validation_result = type_validator.validate(parameter.value.text) type_validation_result = type_validator.validate(parameter.value.text)
if isinstance(type_validation_result, Issue): if isinstance(type_validation_result, Issue):
self.logger.debug('Got issue for parameter "%s" with value "%s"' % (parameter.name.text, parameter.value.text)) self.logger.debug('Got issue for parameter "%s" with value "%s"' % (parameter.name.text, parameter.value.text))
type_validation_result.mark = main_config.mark.merge(parameter.value.start_mark.merge(type_validation_result.mark)) type_validation_result.mark = parameter.value.start_mark.merge(type_validation_result.mark)
results.append(type_validation_result) results.append(type_validation_result)
else: else:
value = type_validation_result value = type_validation_result
if value == parameter_schema.default: if value == parameter_schema.default:
results.append(MarkedIssue(Issue.INFO, 'Parameter "%s" value equals default' % parameter.name.text, main_config.mark.merge(parameter.start_mark))) results.append(MarkedIssue(Issue.INFO, 'Parameter "%s" value equals default' % parameter.name.text, parameter.start_mark))
return results return results

View File

@@ -40,25 +40,13 @@ class OpenstackComponent(object):
if not config_name in self.configs: if not config_name in self.configs:
resource = self.openstack.resource_locator.find_resource(self.host.name, self.name, config_name) resource = self.openstack.resource_locator.find_resource(self.host.name, self.name, config_name)
if resource: if resource:
config = self.openstack.config_parser.parse(config_name, resource.get_contents()) config = self.openstack.config_parser.parse(config_name, Mark(resource.name), resource.get_contents())
config.mark = Mark(resource.name)
self.configs[config_name] = config self.configs[config_name] = config
else: else:
self.configs[config_name] = None self.configs[config_name] = None
return self.configs[config_name] return self.configs[config_name]
class ComponentConfig(object):
def __init__(self, name, mark, sections=[], errors=[]):
super(ComponentConfig, self).__init__()
self.name = name
self.mark = mark
self.sections = sections
for section in self.sections:
section.parent = self
self.errors = errors
class Element(object): class Element(object):
def __init__(self, start_mark, end_mark): def __init__(self, start_mark, end_mark):
self.start_mark = start_mark self.start_mark = start_mark
@@ -70,6 +58,16 @@ class Element(object):
def __ne__(self, other): def __ne__(self, other):
return not self == other return not self == other
class ComponentConfig(Element):
def __init__(self, start_mark, end_mark, name, sections=[], errors=[]):
super(ComponentConfig, self).__init__(start_mark, end_mark)
self.name = name
self.sections = sections
for section in self.sections:
section.parent = self
self.errors = errors
class TextElement(Element): class TextElement(Element):
def __init__(self, start_mark, end_mark, text): def __init__(self, start_mark, end_mark, text):
super(TextElement, self).__init__(start_mark, end_mark) super(TextElement, self).__init__(start_mark, end_mark)