1 # Copyright 2015 Google Inc. All Rights Reserved.
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
18 [{"comment": ignored_value},
19 {"rule_class_name1": {"arg1": value, "arg2": value, ...}},
20 {"rule_class_name2": {"arg1": value, "arg2": value, ...}},
23 [{"comment": "this text is ignored"},
24 {"SendStatus": {"url": "example\\.com/ss.*", "status": 204}},
25 {"ModifyUrl": {"url": "(example\\.com)(/.*)", "new_url": "{1}"}}
33 class Error(Exception):
39 """A parsed sequence of Rule objects."""
41 def __init__(self
, file_obj
=None, allowed_imports
=None):
42 """Initializes from the given file object.
45 file_obj: A file object.
46 allowed_imports: A set of strings, defaults to {'rules'}.
47 Use {'*'} to allow any import path.
49 if allowed_imports
is None:
50 allowed_imports
= {'rules'}
51 self
._rules
= [] if file_obj
is None else _Load(file_obj
, allowed_imports
)
53 def Contains(self
, rule_type_name
):
54 """Returns true if any rule matches the given type name.
57 rule_type_name: a string.
59 True if any rule matches, else False.
61 return any(rule
for rule
in self
._rules
if rule
.IsType(rule_type_name
))
63 def Find(self
, rule_type_name
):
64 """Returns a _Rule object containing all rules with the given type name.
67 rule_type_name: a string.
69 A callable object that expects two arguments:
70 request: the httparchive ArchivedHttpRequest
71 response: the httparchive ArchivedHttpResponse
72 and returns the rule return_value of the first rule that returns
73 should_stop == True, or the last rule's return_value if all rules returns
76 matches
= [rule
for rule
in self
._rules
if rule
.IsType(rule_type_name
)]
80 return _ToString(self
._rules
)
87 """Calls a sequence of Rule objects until one returns should_stop."""
89 def __init__(self
, rules
):
92 def __call__(self
, request
, response
):
93 """Calls the rules until one returns should_stop.
96 request: the httparchive ArchivedHttpRequest.
97 response: the httparchive ArchivedHttpResponse, which may be None.
99 The rule return_value of the first rule that returns should_stop == True,
100 or the last rule's return_value if all rules return should_stop == False.
103 for rule
in self
._rules
:
104 should_stop
, return_value
= rule
.ApplyRule(
105 return_value
, request
, response
)
111 return _ToString(self
._rules
)
117 def _ToString(rules
):
118 """Formats a sequence of Rule objects into a string."""
119 return '[\n%s\n]' % '\n'.join('%s' % rule
for rule
in rules
)
122 def _Load(file_obj
, allowed_imports
):
123 """Parses and evaluates all rules in the given file.
126 file_obj: a file object.
127 allowed_imports: a sequence of strings, e.g.: {'rules'}.
132 entries
= json
.load(file_obj
)
133 if not isinstance(entries
, list):
134 raise Error('Expecting a list, not %s', type(entries
))
135 for i
, entry
in enumerate(entries
):
136 if not isinstance(entry
, dict):
137 raise Error('%s: Expecting a dict, not %s', i
, type(entry
))
139 raise Error('%s: Expecting 1 item, not %d', i
, len(entry
))
140 name
, args
= next(entry
.iteritems())
141 if not isinstance(name
, basestring
):
142 raise Error('%s: Expecting a string TYPE, not %s', i
, type(name
))
143 if not re
.match(r
'(\w+\.)*\w+$', name
):
144 raise Error('%s: Expecting a classname TYPE, not %s', i
, name
)
145 if name
== 'comment':
147 if not isinstance(args
, dict):
148 raise Error('%s: Expecting a dict ARGS, not %s', i
, type(args
))
150 if '.' not in fullname
:
151 fullname
= 'rules.%s' % fullname
153 modulename
, classname
= fullname
.rsplit('.', 1)
154 if '*' not in allowed_imports
and modulename
not in allowed_imports
:
155 raise Error('%s: Package %r is not in allowed_imports', i
, modulename
)
157 module
= __import__(modulename
, fromlist
=[classname
])
158 clazz
= getattr(module
, classname
)
160 missing
= {s
for s
in ('IsType', 'ApplyRule') if not hasattr(clazz
, s
)}
162 raise Error('%s: %s lacks %s', i
, clazz
.__name
__, ' and '.join(missing
))