Commit 06e46fdb authored by John Tourtellott's avatar John Tourtellott
Browse files

Add ACE3P file validator

parent 21ba47e7
#=============================================================================
#
# Copyright (c) Kitware, Inc.
# All rights reserved.
# See LICENSE.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even
# the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the above copyright notice for more information.
#
#=============================================================================
import os
import re
RE_KEYWORD = r'^\s*(\w+)\s*(:)' # left-hand side for either section or assignment
class TokenType:
KEYWORD = 'keyword'
END_SECTION = 'end-section'
NULL = 'null'
class TokenInfo:
def __init__(self, token_type, line_number, keyword=None, rest_of_line=None):
self.type = token_type
self.line_number = line_number
self.keyword = keyword
self.rest_of_line = rest_of_line
def __str__(self):
s = 'Token type {} line {}'.format(self.type, self.line_number)
if self.type == TokenType.KEYWORD:
s = ' keyword {} res_of_line \"{}\"'.format(s, self.keyword, self.rest_of_line)
return s
class Section:
"""Stores contents of individual section"""
def __init__(self, name, first_line):
self.name = name
self.first_line = first_line
self.current_line = 0
self.last_line = None
self.children = list() # children sections
self.data = dict() # <keyword, value>
def __str__(self):
return 'Section {} lines {}-{}'.format(
self.name, self.first_line, self.last_line)
class ACE3PFileValidator:
""""""
def __init__(self, path):
""""""
self.current_pos = 0 # current line number
self.exists = False # file exists
self.lines = list() # file contents
self.sections = dict() # <keyword, set-of-sections>
self.number_of_lines = 0
self._parse_file(path)
def file_exists(self):
return self.exists
def get_number_of_lines(self):
return self.number_of_lines
def get_sections(self, name):
"""Returns set of sections with given name, or None"""
return self.sections.get(name)
def has_section(self, name, min_count=1, max_count=1):
""""""
dict_item = self.sections.get(name)
if dict_item is None:
return False
count = len(dict_item)
if min_count is not None and count < min_count:
return False
if max_count is not None and count > max_count:
return False
# (else)
return True
def section_has_bool(self, section, keyword, bool_value):
value = section.data.get(keyword)
if value is None:
print('{} section has no keyword {}'.format(section.name, keyword))
return False
match_value = '.true.' if bool_value else '.false'
return value == match_value
def section_has_double(self, section, keyword, double_value, tolerance=1e-6):
value = section.data.get(keyword)
if value is None:
print('{} section has no keyword {}'.format(section.name, keyword))
return False
dvalue = float(value) # raises ValueError if not valid
return abs(dvalue - double_value) < tolerance
def section_has_int(self, section, keyword, int_value):
value = section.data.get(keyword)
if value is None:
print('{} section has no keyword {}'.format(section.name, keyword))
return False
ivalue = int(value) # raises ValueError if not valid
return ivalue == int_value
def section_has_string(self, section, keyword, string_value):
value = section.data.get(keyword)
if value is None:
print('{} section has no keyword {}'.format(section.name, keyword))
return False
return value == string_value
def _get_next_line(self):
""""""
while self.lines:
raw_line = self.lines.pop()
self.current_pos += 1
line = raw_line.strip()
if len(line) == 0 or line.startswith('//'):
continue
# (else)
# print(self.current_pos, line)
return line
# End of input
return None
def _get_next_token(self):
"""Parse input until next token
Should be one of: keyword, end of section brace, end of input
"""
line = self._get_next_line()
while line is not None:
match = re.match(RE_KEYWORD, line)
if match:
keyword = match.group(1)
rest = line[match.end(2):].strip()
return TokenInfo(TokenType.KEYWORD, self.current_pos, keyword=keyword, rest_of_line=rest)
match = re.match('\s*}', line)
if match:
return TokenInfo(TokenType.END_SECTION, self.current_pos)
line = self._get_next_line()
return TokenInfo(TokenType.NULL, self.current_pos)
def _is_new_section(self, info):
""""""
rest = info.rest_of_line.strip()
return rest == '' or rest.startswith('{')
def _parse_file(self, path):
if not os.path.exists(path):
self.exists = False
return;
self.exists = True
with open(path) as fp:
self.lines = fp.readlines()
self.number_of_lines = len(self.lines)
# To traverse, reverse self.lines and pop one line at a time
# until self.lines is empty
self.lines.reverse()
# current_section = None
while self.lines:
info = self._get_next_token()
if info.type == TokenType.NULL:
break
if info.type == TokenType.KEYWORD:
# print('* Keyword', info.keyword)
if self._is_new_section(info):
self._parse_section(info)
def _parse_section(self, token_info):
"""Parse contents of section, which can be sub-sections and/or assignments"""
# print('** Start section', token_info.keyword)
# Parse forward to the starting brace
self._parse_section_start(token_info)
# Initialize section
name = token_info.keyword
section = Section(name, token_info.line_number)
dict_item = self.sections.get(name)
if dict_item is None:
dict_item = set()
dict_item.add(section)
self.sections[name] = dict_item
# Continue parsing for assignments until ending brace
next_info = self._get_next_token()
while next_info:
# print('next_info', next_info)
if next_info.type == TokenType.END_SECTION:
# print('**End section', name)
return
if next_info.type == TokenType.NULL:
raise RuntimeError('Unexpected end of file parsing section')
if not next_info.type == TokenType.KEYWORD:
raise RuntimeError('Unexpected token type {}'.format(next_info.type))
if self._is_new_section(next_info):
# Parse subsection
child_section = self._parse_section(next_info)
section.children.append(child_section)
else:
# Add regular assignment to section data
section.data[next_info.keyword] = next_info.rest_of_line
next_info = self._get_next_token()
return section
def _parse_section_start(self, token_info):
"""Parses to the end of line with section start brace ('{')"""
if token_info.rest_of_line.strip().startswith('{'):
return
line = self._get_next_line()
while line:
if line.strip().startswith('{'):
return
line = self._get_next_line()
raise RuntimeError('expected left brace but reached end of file')
......@@ -20,9 +20,9 @@ import smtk.io
import smtk.operation
import smtk.resource
import smtk.session.vtk
import smtk.testing
from ACE3PFileValidator import ACE3PFileValidator
import util
OPERATION_SUCCEEDED = int(smtk.operation.Operation.SUCCEEDED) # 3
......@@ -168,9 +168,13 @@ class Omega3PTest1(smtk.testing.TestCase):
expected_path = os.path.join(export_path, expected_filename)
self.assertTrue(os.path.exists(expected_path))
# Sanity check number of lines
num_lines = len(open(expected_path).readlines())
self.assertGreater(num_lines, 12)
# Validate result
validator = ACE3PFileValidator(expected_path)
self.assertTrue(validator.file_exists())
self.assertGreater(validator.get_number_of_lines(), 12)
for name in ['ModelInfo', 'BoundaryCondition', 'FiniteElement', 'EigenSolver']:
msg = 'output includes {} section'.format(name)
self.assertTrue(validator.has_section(name), msg)
# (On success) remove the export folder
shutil.rmtree(export_path)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment