Robot Framework
search.py
Go to the documentation of this file.
1 # Copyright 2008-2015 Nokia Networks
2 # Copyright 2016- Robot Framework Foundation
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 
16 import re
17 
18 from robot.errors import VariableError
19 from robot.utils import is_string
20 
21 
22 def search_variable(string, identifiers='$@&%*', ignore_errors=False):
23  if not (is_string(string) and '{' in string):
24  return VariableMatch(string)
25  return _search_variable(string, identifiers, ignore_errors)
26 
27 
28 def contains_variable(string, identifiers='$@&'):
29  match = search_variable(string, identifiers, ignore_errors=True)
30  return bool(match)
31 
32 
33 def is_variable(string, identifiers='$@&'):
34  match = search_variable(string, identifiers, ignore_errors=True)
35  return match.is_variable()
36 
37 
38 def is_scalar_variable(string):
39  return is_variable(string, '$')
40 
41 
42 def is_list_variable(string):
43  return is_variable(string, '@')
44 
45 
46 def is_dict_variable(string):
47  return is_variable(string, '&')
48 
49 
50 def is_assign(string, identifiers='$@&', allow_assign_mark=False):
51  match = search_variable(string, identifiers, ignore_errors=True)
52  return match.is_assign(allow_assign_mark)
53 
54 
55 def is_scalar_assign(string, allow_assign_mark=False):
56  return is_assign(string, '$', allow_assign_mark)
57 
58 
59 def is_list_assign(string, allow_assign_mark=False):
60  return is_assign(string, '@', allow_assign_mark)
61 
62 
63 def is_dict_assign(string, allow_assign_mark=False):
64  return is_assign(string, '&', allow_assign_mark)
65 
66 
68 
69  def __init__(self, string, identifier=None, base=None, items=(), start=-1, end=-1):
70  self.stringstring = string
71  self.identifieridentifier = identifier
72  self.basebase = base
73  self.itemsitems = items
74  self.startstart = start
75  self.endend = end
76 
77  def resolve_base(self, variables, ignore_errors=False):
78  if self.identifieridentifier:
79  internal = search_variable(self.basebase)
80  self.basebase = variables.replace_string(
81  internal,
82  custom_unescaper=unescape_variable_syntax,
83  ignore_errors=ignore_errors,
84  )
85 
86  @property
87  name = property
88 
89  def name(self):
90  return '%s{%s}' % (self.identifieridentifier, self.basebase) if self else None
91 
92  @property
93  before = property
94 
95  def before(self):
96  return self.stringstring[:self.startstart] if self.identifieridentifier else self.stringstring
97 
98  @property
99  match = property
100 
101  def match(self):
102  return self.stringstring[self.startstart:self.endend] if self.identifieridentifier else None
103 
104  @property
105  after = property
106 
107  def after(self):
108  return self.stringstring[self.endend:] if self.identifieridentifier else None
109 
110  def is_variable(self):
111  return bool(self.identifieridentifier
112  and self.basebase
113  and self.startstart == 0
114  and self.endend == len(self.stringstring))
115 
117  return self.identifieridentifier == '$' and self.is_variableis_variable()
118 
119  def is_list_variable(self):
120  return self.identifieridentifier == '@' and self.is_variableis_variable()
121 
122  def is_dict_variable(self):
123  return self.identifieridentifier == '&' and self.is_variableis_variable()
124 
125  def is_assign(self, allow_assign_mark=False):
126  if allow_assign_mark and self.stringstring.endswith('='):
127  match = search_variable(self.stringstring[:-1].rstrip(), ignore_errors=True)
128  return match.is_assign()
129  return (self.is_variableis_variable()
130  and self.identifieridentifier in '$@&'
131  and not self.itemsitems
132  and not search_variable(self.basebase))
133 
134  def is_scalar_assign(self, allow_assign_mark=False):
135  return self.identifieridentifier == '$' and self.is_assignis_assign(allow_assign_mark)
136 
137  def is_list_assign(self, allow_assign_mark=False):
138  return self.identifieridentifier == '@' and self.is_assignis_assign(allow_assign_mark)
139 
140  def is_dict_assign(self, allow_assign_mark=False):
141  return self.identifieridentifier == '&' and self.is_assignis_assign(allow_assign_mark)
142 
143  def __bool__(self):
144  return self.identifieridentifier is not None
145 
146  def __str__(self):
147  if not self:
148  return '<no match>'
149  items = ''.join('[%s]' % i for i in self.itemsitems) if self.itemsitems else ''
150  return '%s{%s}%s' % (self.identifieridentifier, self.basebase, items)
151 
152 
153 def _search_variable(string, identifiers, ignore_errors=False):
154  start = _find_variable_start(string, identifiers)
155  if start < 0:
156  return VariableMatch(string)
157 
158  match = VariableMatch(string, identifier=string[start], start=start)
159  left_brace, right_brace = '{', '}'
160  open_braces = 1
161  escaped = False
162  items = []
163  indices_and_chars = enumerate(string[start+2:], start=start+2)
164 
165  for index, char in indices_and_chars:
166  if char == left_brace and not escaped:
167  open_braces += 1
168 
169  elif char == right_brace and not escaped:
170  open_braces -= 1
171 
172  if open_braces == 0:
173  next_char = string[index+1] if index+1 < len(string) else None
174 
175  if left_brace == '{': # Parsing name.
176  match.base = string[start+2:index]
177  if match.identifier not in '$@&' or next_char != '[':
178  match.end = index + 1
179  break
180  left_brace, right_brace = '[', ']'
181 
182  else: # Parsing items.
183  items.append(string[start+1:index])
184  if next_char != '[':
185  match.end = index + 1
186  match.items = tuple(items)
187  break
188 
189  next(indices_and_chars) # Consume '['.
190  start = index + 1 # Start of the next item.
191  open_braces = 1
192 
193  else:
194  escaped = False if char != '\\' else not escaped
195 
196  if open_braces:
197  if ignore_errors:
198  return VariableMatch(string)
199  incomplete = string[match.start:]
200  if left_brace == '{':
201  raise VariableError(f"Variable '{incomplete}' was not closed properly.")
202  raise VariableError(f"Variable item '{incomplete}' was not closed properly.")
203 
204  return match if match else VariableMatch(match)
205 
206 
207 def _find_variable_start(string, identifiers):
208  index = 1
209  while True:
210  index = string.find('{', index) - 1
211  if index < 0:
212  return -1
213  if string[index] in identifiers and _not_escaped(string, index):
214  return index
215  index += 2
216 
217 
218 def _not_escaped(string, index):
219  escaped = False
220  while index > 0 and string[index-1] == '\\':
221  index -= 1
222  escaped = not escaped
223  return not escaped
224 
225 
227 
228  def handle_escapes(match):
229  escapes, text = match.groups()
230  if len(escapes) % 2 == 1 and starts_with_variable_or_curly(text):
231  return escapes[1:]
232  return escapes
233 
234  def starts_with_variable_or_curly(text):
235  if text[0] in '{}':
236  return True
237  match = search_variable(text, ignore_errors=True)
238  return match and match.start == 0
239 
240  return re.sub(r'(\\+)(?=(.+))', handle_escapes, item)
241 
242 
244 
245  def __init__(self, string, identifiers='$@&%', ignore_errors=False):
246  self.stringstring = string
247  self.identifiersidentifiers = identifiers
248  self.ignore_errorsignore_errors = ignore_errors
249 
250  def __iter__(self):
251  remaining = self.stringstring
252  while True:
253  match = search_variable(remaining, self.identifiersidentifiers, self.ignore_errorsignore_errors)
254  if not match:
255  break
256  remaining = match.after
257  yield match.before, match.match, remaining
258 
259  def __len__(self):
260  return sum(1 for _ in self)
261 
262  def __bool__(self):
263  try:
264  next(iter(self))
265  except StopIteration:
266  return False
267  else:
268  return True
Used when variable does not exist.
Definition: errors.py:72
def __init__(self, string, identifiers='$@&%', ignore_errors=False)
Definition: search.py:245
def is_assign(self, allow_assign_mark=False)
Definition: search.py:125
def resolve_base(self, variables, ignore_errors=False)
Definition: search.py:77
def is_list_assign(self, allow_assign_mark=False)
Definition: search.py:137
def __init__(self, string, identifier=None, base=None, items=(), start=-1, end=-1)
Definition: search.py:69
def is_dict_assign(self, allow_assign_mark=False)
Definition: search.py:140
def is_scalar_assign(self, allow_assign_mark=False)
Definition: search.py:134
def is_scalar_variable(string)
Definition: search.py:38
def is_list_variable(string)
Definition: search.py:42
def contains_variable(string, identifiers='$@&')
Definition: search.py:28
def _search_variable(string, identifiers, ignore_errors=False)
Definition: search.py:153
def unescape_variable_syntax(item)
Definition: search.py:226
def is_dict_assign(string, allow_assign_mark=False)
Definition: search.py:63
def _find_variable_start(string, identifiers)
Definition: search.py:207
def _not_escaped(string, index)
Definition: search.py:218
def search_variable(string, identifiers='$@&% *', ignore_errors=False)
Definition: search.py:22
def is_dict_variable(string)
Definition: search.py:46
def is_scalar_assign(string, allow_assign_mark=False)
Definition: search.py:55
def is_list_assign(string, allow_assign_mark=False)
Definition: search.py:59
def is_variable(string, identifiers='$@&')
Definition: search.py:33
def is_assign(string, identifiers='$@&', allow_assign_mark=False)
Definition: search.py:50