Better reporting syntax error positions in config files
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
@@ -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")
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -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)
|
||||||
|
Reference in New Issue
Block a user