blob: cf1a8e4dba063eb777185e608ff9d71343dc4ba1 [file] [log] [blame]
Chih-Yu Huang08c6e9d2015-11-25 19:06:07 +08001#!/usr/bin/python
2# -*- coding: utf-8 -*-
3# Copyright 2015 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""The module is for parsing data in the test plan.
Chih-Yu Huang68ab9f02016-12-19 12:18:26 +08008
Chih-Yu Huang08c6e9d2015-11-25 19:06:07 +08009We implement a parse function to convert string to required data type, and data
10checkers for checking the data is valid.
11"""
12
Chih-Yu Huang08c6e9d2015-11-25 19:06:07 +080013import re
14import types
15
16
17LEFT_BRACKETS = ['[', '(']
18RIGHT_BRACKETS_DICT = {'[': ']', '(': ')'}
19BUILDER_DICT = {'[': list, '(': tuple}
20
21
22def Parse(string, checker=None):
Chih-Yu Huang68ab9f02016-12-19 12:18:26 +080023 """Converts the string to the python type.
Chih-Yu Huange9150972016-01-27 18:32:05 +080024
25 We only support tuple, list, None, int, float, and str.
26 There are some restrictions for input:
Chih-Yu Huang08c6e9d2015-11-25 19:06:07 +080027 1. str only contains [0-9A-Za-z_]
28 2. nested structure is not allowed.
29 3. Ignore all space.
Chih-Yu Huange9150972016-01-27 18:32:05 +080030
31 Args:
32 string: The string to be parsed.
33 checker: The rule of the result. None if no rule for the result.
Chih-Yu Huang68ab9f02016-12-19 12:18:26 +080034
Chih-Yu Huange9150972016-01-27 18:32:05 +080035 Returns:
36 The parsed result.
Chih-Yu Huang68ab9f02016-12-19 12:18:26 +080037
Chih-Yu Huange9150972016-01-27 18:32:05 +080038 Raise:
39 ValueError: if the result does not meet the rule.
Chih-Yu Huang08c6e9d2015-11-25 19:06:07 +080040 """
41 def _ParseLiteral(string):
42 string = string.strip()
43 if string in ['', 'None', 'none', 'null']:
44 return None
45 try:
46 return int(string)
47 except ValueError:
48 pass
49 try:
50 return float(string)
51 except ValueError:
52 pass
53 match = re.match(r'\w+', string)
54 if not match or match.group(0) != string:
Chih-Yu Huange9150972016-01-27 18:32:05 +080055 raise ValueError('String cannot be parsed as literal: %s', string)
Chih-Yu Huang08c6e9d2015-11-25 19:06:07 +080056 return string
57
58 def _Parse(string):
59 string = string.strip()
60 if string == '':
61 return None
62 if string[0] not in LEFT_BRACKETS:
63 return _ParseLiteral(string)
64 else:
65 left, items, right = string[0], string[1:-1], string[-1]
66 if right != RIGHT_BRACKETS_DICT[left]:
Chih-Yu Huange9150972016-01-27 18:32:05 +080067 raise ValueError('The right bracket is not found: %s', string)
cyuehdbaec982019-12-09 13:20:38 +080068 literals = list(map(_ParseLiteral, items.split(',')))
69 return BUILDER_DICT[left](literals)
Chih-Yu Huang08c6e9d2015-11-25 19:06:07 +080070
71 result = _Parse(string)
72 if checker is not None and checker.CheckData(result) is False:
Chih-Yu Huange9150972016-01-27 18:32:05 +080073 raise ValueError('result %s is not in checker %s' % (result, checker))
Chih-Yu Huang08c6e9d2015-11-25 19:06:07 +080074 return result
75
76
77class CheckerBase(object):
78 """Virtual class for data checker."""
79 def CheckData(self, data):
80 raise NotImplementedError
81
82
83class LiteralChecker(CheckerBase):
Chih-Yu Huang68ab9f02016-12-19 12:18:26 +080084 """Checks whether the literal data matches one of the rules.
85
Chih-Yu Huang08c6e9d2015-11-25 19:06:07 +080086 Data should not be list or tuple.
87 Each rule might be a type (ex: int, str) or a value.
88
89 For example:
90 LiteralChecker([int, 'foo']) would check whether data is a integer or 'foo'.
91 """
92 def __init__(self, rules):
93 if type(rules) != list:
94 rules = [rules]
95 self.rules = rules
96
Chih-Yu Huangea32e702016-02-17 16:14:56 +080097 def RuleStr(self):
Chih-Yu Huang08c6e9d2015-11-25 19:06:07 +080098 return str(self.rules)
99
Chih-Yu Huangea32e702016-02-17 16:14:56 +0800100 def __str__(self):
101 return 'LiteralChecker(%s)' % self.RuleStr()
102
Chih-Yu Huang08c6e9d2015-11-25 19:06:07 +0800103 @staticmethod
104 def _IsMatch(data, rule):
105 if type(rule) == type:
106 return type(data) == rule
107 if type(rule) == types.FunctionType:
108 return rule(data)
109 return data == rule
110
111 def CheckData(self, data):
112 return any([self._IsMatch(data, rule) for rule in self.rules])
113
114
115class TupleChecker(CheckerBase):
Chih-Yu Huang68ab9f02016-12-19 12:18:26 +0800116 """Checks whether every field of the data matches the corresponding rules.
117
Chih-Yu Huang08c6e9d2015-11-25 19:06:07 +0800118 Data type should be tuple, the length of the tuple should be the same as the
119 length of the rules_list.
120
121 For example:
122 TupleChecker([[int, 'foo'], [float, None]]) means the valid data should
123 be 2-field tuple. First field should be a integer or 'foo', and the second
124 field should be a float or None.
125 """
126 def __init__(self, rules_list):
127 self.checkers = []
128 for rules in rules_list:
129 self.checkers.append(LiteralChecker(rules))
130
Chih-Yu Huangea32e702016-02-17 16:14:56 +0800131 def RuleStr(self):
132 return ', '.join([checker.RuleStr() for checker in self.checkers])
133
134 def __str__(self):
135 return 'TupleChecker(%s)' % self.RuleStr()
136
Chih-Yu Huang08c6e9d2015-11-25 19:06:07 +0800137 def CheckData(self, data):
138 if type(data) != tuple or len(data) != len(self.checkers):
139 return False
140 return all([checker.CheckData(item)
141 for checker, item in zip(self.checkers, data)])
142
143
144class ListChecker(CheckerBase):
Chih-Yu Huang68ab9f02016-12-19 12:18:26 +0800145 """Checks whether all items in the data matches the rules.
146
Chih-Yu Huang08c6e9d2015-11-25 19:06:07 +0800147 Data type should be list or literal, the length of the data is not limited.
148
149 For example:
150 ListChecker([int, 'foo']) means the valid data should be contain integer or
151 'foo' only.
152 """
153 def __init__(self, rules):
154 self.checker = LiteralChecker(rules)
155
Chih-Yu Huangea32e702016-02-17 16:14:56 +0800156 def RuleStr(self):
157 return self.checker.RuleStr()
158
159 def __str__(self):
160 return 'ListChecker(%s)' % self.RuleStr()
161
Chih-Yu Huang08c6e9d2015-11-25 19:06:07 +0800162 def CheckData(self, data):
163 if type(data) != list:
164 data = [data]
165 return all(map(self.checker.CheckData, data))