blob: cf1a8e4dba063eb777185e608ff9d71343dc4ba1 [file] [log] [blame]
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright 2015 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""The module is for parsing data in the test plan.
We implement a parse function to convert string to required data type, and data
checkers for checking the data is valid.
"""
import re
import types
LEFT_BRACKETS = ['[', '(']
RIGHT_BRACKETS_DICT = {'[': ']', '(': ')'}
BUILDER_DICT = {'[': list, '(': tuple}
def Parse(string, checker=None):
"""Converts the string to the python type.
We only support tuple, list, None, int, float, and str.
There are some restrictions for input:
1. str only contains [0-9A-Za-z_]
2. nested structure is not allowed.
3. Ignore all space.
Args:
string: The string to be parsed.
checker: The rule of the result. None if no rule for the result.
Returns:
The parsed result.
Raise:
ValueError: if the result does not meet the rule.
"""
def _ParseLiteral(string):
string = string.strip()
if string in ['', 'None', 'none', 'null']:
return None
try:
return int(string)
except ValueError:
pass
try:
return float(string)
except ValueError:
pass
match = re.match(r'\w+', string)
if not match or match.group(0) != string:
raise ValueError('String cannot be parsed as literal: %s', string)
return string
def _Parse(string):
string = string.strip()
if string == '':
return None
if string[0] not in LEFT_BRACKETS:
return _ParseLiteral(string)
else:
left, items, right = string[0], string[1:-1], string[-1]
if right != RIGHT_BRACKETS_DICT[left]:
raise ValueError('The right bracket is not found: %s', string)
literals = list(map(_ParseLiteral, items.split(',')))
return BUILDER_DICT[left](literals)
result = _Parse(string)
if checker is not None and checker.CheckData(result) is False:
raise ValueError('result %s is not in checker %s' % (result, checker))
return result
class CheckerBase(object):
"""Virtual class for data checker."""
def CheckData(self, data):
raise NotImplementedError
class LiteralChecker(CheckerBase):
"""Checks whether the literal data matches one of the rules.
Data should not be list or tuple.
Each rule might be a type (ex: int, str) or a value.
For example:
LiteralChecker([int, 'foo']) would check whether data is a integer or 'foo'.
"""
def __init__(self, rules):
if type(rules) != list:
rules = [rules]
self.rules = rules
def RuleStr(self):
return str(self.rules)
def __str__(self):
return 'LiteralChecker(%s)' % self.RuleStr()
@staticmethod
def _IsMatch(data, rule):
if type(rule) == type:
return type(data) == rule
if type(rule) == types.FunctionType:
return rule(data)
return data == rule
def CheckData(self, data):
return any([self._IsMatch(data, rule) for rule in self.rules])
class TupleChecker(CheckerBase):
"""Checks whether every field of the data matches the corresponding rules.
Data type should be tuple, the length of the tuple should be the same as the
length of the rules_list.
For example:
TupleChecker([[int, 'foo'], [float, None]]) means the valid data should
be 2-field tuple. First field should be a integer or 'foo', and the second
field should be a float or None.
"""
def __init__(self, rules_list):
self.checkers = []
for rules in rules_list:
self.checkers.append(LiteralChecker(rules))
def RuleStr(self):
return ', '.join([checker.RuleStr() for checker in self.checkers])
def __str__(self):
return 'TupleChecker(%s)' % self.RuleStr()
def CheckData(self, data):
if type(data) != tuple or len(data) != len(self.checkers):
return False
return all([checker.CheckData(item)
for checker, item in zip(self.checkers, data)])
class ListChecker(CheckerBase):
"""Checks whether all items in the data matches the rules.
Data type should be list or literal, the length of the data is not limited.
For example:
ListChecker([int, 'foo']) means the valid data should be contain integer or
'foo' only.
"""
def __init__(self, rules):
self.checker = LiteralChecker(rules)
def RuleStr(self):
return self.checker.RuleStr()
def __str__(self):
return 'ListChecker(%s)' % self.RuleStr()
def CheckData(self, data):
if type(data) != list:
data = [data]
return all(map(self.checker.CheckData, data))