Robot Framework
blocks.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 ast
17 
18 from robot.utils import file_writer, is_pathlike, is_string
19 
20 from .statements import Comment, EmptyLine
21 from .visitor import ModelVisitor
22 from ..lexer import Token
23 
24 
25 class Block(ast.AST):
26 
29  _fields = ()
30 
33  _attributes = ('lineno', 'col_offset', 'end_lineno', 'end_col_offset', 'errors')
34  errors = ()
35 
36  @property
37  lineno = property
38 
39  def lineno(self):
40  statement = FirstStatementFinder.find_from(self)
41  return statement.lineno if statement else -1
42 
43  @property
44  col_offset = property
45 
46  def col_offset(self):
47  statement = FirstStatementFinder.find_from(self)
48  return statement.col_offset if statement else -1
49 
50  @property
51  end_lineno = property
52 
53  def end_lineno(self):
54  statement = LastStatementFinder.find_from(self)
55  return statement.end_lineno if statement else -1
56 
57  @property
58  end_col_offset = property
59 
60  def end_col_offset(self):
61  statement = LastStatementFinder.find_from(self)
62  return statement.end_col_offset if statement else -1
63 
64  def validate_model(self):
65  ModelValidator().visit(self)
66 
67  def validate(self, context):
68  pass
69 
70  def _body_is_empty(self):
71  for node in self.body:
72  if not isinstance(node, (EmptyLine, Comment)):
73  return False
74  return True
75 
76 
78 
81  _fields = ('header', 'body')
82 
83  def __init__(self, header, body=None, errors=()):
84  self.headerheader = header
85  self.bodybody = body or []
86  self.errorserrorserrors = errors
87 
88 
89 class File(Block):
90 
93  _fields = ('sections',)
94 
97  _attributes = ('source', 'languages') + Block._attributes
98 
99  def __init__(self, sections=None, source=None, languages=()):
100  self.sectionssections = sections or []
101  self.sourcesource = source
102  self.languageslanguages = languages
103 
104 
110  def save(self, output=None):
111  output = output or self.sourcesource
112  if output is None:
113  raise TypeError('Saving model requires explicit output '
114  'when original source is not path.')
115  ModelWriter(output).write(self)
116 
117 
118 class Section(Block):
119 
122  _fields = ('header', 'body')
123 
124  def __init__(self, header=None, body=None):
125  self.headerheader = header
126  self.bodybody = body or []
127 
128 
130  pass
131 
132 
133 class VariableSection(Section):
134  pass
135 
136 
137 # FIXME: should there be a separate TaskSection?
139 
140  @property
141  tasks = property
142 
143  def tasks(self):
144  return self.headerheader.type == Token.TASK_HEADER
145 
146 
148  pass
149 
150 
151 class CommentSection(Section):
152  pass
153 
154 
156 
159  _fields = ('header', 'body')
160 
161  def __init__(self, header, body=None):
162  self.headerheader = header
163  self.bodybody = body or []
164 
165  @property
166  name = property
167 
168  def name(self):
169  return self.headerheader.name
170 
171 
172 class Keyword(Block):
173 
176  _fields = ('header', 'body')
177 
178  def __init__(self, header, body=None):
179  self.headerheader = header
180  self.bodybody = body or []
181 
182  @property
183  name = property
184 
185  def name(self):
186  return self.headerheader.name
187 
188 
189 
194 class If(Block):
195 
198  _fields = ('header', 'body', 'orelse', 'end')
199 
200  def __init__(self, header, body=None, orelse=None, end=None, errors=()):
201  self.headerheader = header
202  self.bodybody = body or []
203  self.orelseorelse = orelse
204  self.endend = end
205  self.errorserrorserrors = errors
206 
207  @property
208  type = property
209 
210  def type(self):
211  return self.headerheader.type
212 
213  @property
214  condition = property
215 
216  def condition(self):
217  return self.headerheader.condition
218 
219  @property
220  assign = property
221 
222  def assign(self):
223  return self.headerheader.assign
224 
225  def validate(self, context):
226  self._validate_body_validate_body()
227  if self.typetypetype == Token.IF:
228  self._validate_structure_validate_structure()
229  self._validate_end_validate_end()
230  if self.typetypetype == Token.INLINE_IF:
231  self._validate_structure_validate_structure()
232  self._validate_inline_if_validate_inline_if()
233 
234  def _validate_body(self):
235  if self._body_is_empty_body_is_empty():
236  type = self.typetypetype if self.typetypetype != Token.INLINE_IF else 'IF'
237  self.errorserrorserrors += (f'{type} branch cannot be empty.',)
238 
240  orelse = self.orelseorelse
241  else_seen = False
242  while orelse:
243  if else_seen:
244  if orelse.type == Token.ELSE:
245  error = 'Only one ELSE allowed.'
246  else:
247  error = 'ELSE IF not allowed after ELSE.'
248  if error not in self.errorserrorserrors:
249  self.errorserrorserrors += (error,)
250  else_seen = else_seen or orelse.type == Token.ELSE
251  orelse = orelse.orelse
252 
253  def _validate_end(self):
254  if not self.endend:
255  self.errorserrorserrors += ('IF must have closing END.',)
256 
258  branch = self
259  assign = branch.assign
260  while branch:
261  if branch.body:
262  item = branch.body[0]
263  if assign and item.type != Token.KEYWORD:
264  self.errorserrorserrors += ('Inline IF with assignment can only contain '
265  'keyword calls.',)
266  if getattr(item, 'assign', None):
267  self.errorserrorserrors += ('Inline IF branches cannot contain assignments.',)
268  if item.type == Token.INLINE_IF:
269  self.errorserrorserrors += ('Inline IF cannot be nested.',)
270  branch = branch.orelse
271 
272 
273 class For(Block):
274 
277  _fields = ('header', 'body', 'end')
278 
279  def __init__(self, header, body=None, end=None, errors=()):
280  self.headerheader = header
281  self.bodybody = body or []
282  self.endend = end
283  self.errorserrorserrors = errors
284 
285  @property
286  variables = property
287 
288  def variables(self):
289  return self.headerheader.variables
290 
291  @property
292  values = property
293 
294  def values(self):
295  return self.headerheader.values
296 
297  @property
298  flavor = property
299 
300  def flavor(self):
301  return self.headerheader.flavor
302 
303  def validate(self, context):
304  if self._body_is_empty_body_is_empty():
305  self.errorserrorserrors += ('FOR loop cannot be empty.',)
306  if not self.endend:
307  self.errorserrorserrors += ('FOR loop must have closing END.',)
308 
309 
310 class Try(Block):
311 
314  _fields = ('header', 'body', 'next', 'end')
315 
316  def __init__(self, header, body=None, next=None, end=None, errors=()):
317  self.headerheader = header
318  self.bodybody = body or []
319  self.nextnext = next
320  self.endend = end
321  self.errorserrorserrors = errors
322 
323  @property
324  type = property
325 
326  def type(self):
327  return self.headerheader.type
328 
329  @property
330  patterns = property
331 
332  def patterns(self):
333  return getattr(self.headerheader, 'patterns', ())
334 
335  @property
336  pattern_type = property
337 
338  def pattern_type(self):
339  return getattr(self.headerheader, 'pattern_type', None)
340 
341  @property
342  variable = property
343 
344  def variable(self):
345  return getattr(self.headerheader, 'variable', None)
346 
347  def validate(self, context):
348  self._validate_body_validate_body()
349  if self.typetypetype == Token.TRY:
350  self._validate_structure_validate_structure()
351  self._validate_end_validate_end()
352 
353  def _validate_body(self):
354  if self._body_is_empty_body_is_empty():
355  self.errorserrorserrors += (f'{self.type} branch cannot be empty.',)
356 
358  else_count = 0
359  finally_count = 0
360  except_count = 0
361  empty_except_count = 0
362  branch = self.nextnext
363  while branch:
364  if branch.type == Token.EXCEPT:
365  if else_count:
366  self.errorserrorserrors += ('EXCEPT not allowed after ELSE.',)
367  if finally_count:
368  self.errorserrorserrors += ('EXCEPT not allowed after FINALLY.',)
369  if branch.patterns and empty_except_count:
370  self.errorserrorserrors += ('EXCEPT without patterns must be last.',)
371  if not branch.patterns:
372  empty_except_count += 1
373  except_count += 1
374  if branch.type == Token.ELSE:
375  if finally_count:
376  self.errorserrorserrors += ('ELSE not allowed after FINALLY.',)
377  else_count += 1
378  if branch.type == Token.FINALLY:
379  finally_count += 1
380  branch = branch.next
381  if finally_count > 1:
382  self.errorserrorserrors += ('Only one FINALLY allowed.',)
383  if else_count > 1:
384  self.errorserrorserrors += ('Only one ELSE allowed.',)
385  if empty_except_count > 1:
386  self.errorserrorserrors += ('Only one EXCEPT without patterns allowed.',)
387  if not (except_count or finally_count):
388  self.errorserrorserrors += ('TRY structure must have EXCEPT or FINALLY branch.',)
389 
390  def _validate_end(self):
391  if not self.endend:
392  self.errorserrorserrors += ('TRY must have closing END.',)
393 
394 
395 class While(Block):
396 
399  _fields = ('header', 'body', 'end')
400 
401  def __init__(self, header, body=None, end=None, errors=()):
402  self.headerheader = header
403  self.bodybody = body or []
404  self.endend = end
405  self.errorserrorserrors = errors
406 
407  @property
408  condition = property
409 
410  def condition(self):
411  return self.headerheader.condition
412 
413  @property
414  limit = property
415 
416  def limit(self):
417  return self.headerheader.limit
418 
419  def validate(self, context):
420  if self._body_is_empty_body_is_empty():
421  self.errorserrorserrors += ('WHILE loop cannot be empty.',)
422  if not self.endend:
423  self.errorserrorserrors += ('WHILE loop must have closing END.',)
424 
425 
427 
428  def __init__(self, output):
429  if is_string(output) or is_pathlike(output):
430  self.writerwriter = file_writer(output)
431  self.close_writerclose_writer = True
432  else:
433  self.writerwriter = output
434  self.close_writerclose_writer = False
435 
436  def write(self, model):
437  try:
438  self.visitvisit(model)
439  finally:
440  if self.close_writerclose_writer:
441  self.writerwriter.close()
442 
443  def visit_Statement(self, statement):
444  for token in statement.tokens:
445  self.writerwriter.write(token.value)
446 
447 
449 
450  def __init__(self):
451  self._context_context = ValidationContext()
452 
453  def visit_Block(self, node):
454  self._context_context.start_block(node)
455  node.validate(self._context_context)
456  ModelVisitor.generic_visit(self, node)
457  self._context_context.end_block()
458 
459  def visit_Try(self, node):
460  if node.header.type == Token.FINALLY:
461  self._context_context.in_finally = True
462  self.visit_Blockvisit_Block(node)
463  self._context_context.in_finally = False
464 
465  def visit_Statement(self, node):
466  node.validate(self._context_context)
467  ModelVisitor.generic_visit(self, node)
468 
469 
471 
472  def __init__(self):
473  self.rootsroots = []
474  self.in_finallyin_finally = False
475 
476  def start_block(self, node):
477  self.rootsroots.append(node)
478 
479  def end_block(self):
480  self.rootsroots.pop()
481 
482  @property
483  in_keyword = property
484 
485  def in_keyword(self):
486  return Keyword in [type(r) for r in self.rootsroots]
487 
488  @property
489  in_for = property
490 
491  def in_for(self):
492  return For in [type(r) for r in self.rootsroots]
493 
494  @property
495  in_while = property
496 
497  def in_while(self):
498  return While in [type(r) for r in self.rootsroots]
499 
500 
502 
503  def __init__(self):
504  self.statementstatement = None
505 
506  @classmethod
507  def find_from(cls, model):
508  finder = cls()
509  finder.visit(model)
510  return finder.statement
511 
512  def visit_Statement(self, statement):
513  if self.statementstatement is None:
514  self.statementstatement = statement
515 
516  def generic_visit(self, node):
517  if self.statementstatement is None:
518  ModelVisitor.generic_visit(self, node)
519 
520 
522 
523  def __init__(self):
524  self.statementstatement = None
525 
526  @classmethod
527  def find_from(cls, model):
528  finder = cls()
529  finder.visit(model)
530  return finder.statement
531 
532  def visit_Statement(self, statement):
533  self.statementstatement = statement
def validate(self, context)
Definition: blocks.py:67
def save(self, output=None)
Save model to the given output or to the original source file.
Definition: blocks.py:110
def __init__(self, sections=None, source=None, languages=())
Definition: blocks.py:99
def validate(self, context)
Definition: blocks.py:303
def __init__(self, header, body=None, end=None, errors=())
Definition: blocks.py:279
def __init__(self, header, body=None, errors=())
Definition: blocks.py:83
Represents IF structures in the model.
Definition: blocks.py:194
def validate(self, context)
Definition: blocks.py:225
def __init__(self, header, body=None, orelse=None, end=None, errors=())
Definition: blocks.py:200
def __init__(self, header, body=None)
Definition: blocks.py:178
def visit_Statement(self, statement)
Definition: blocks.py:443
def __init__(self, header=None, body=None)
Definition: blocks.py:124
def __init__(self, header, body=None)
Definition: blocks.py:161
def __init__(self, header, body=None, next=None, end=None, errors=())
Definition: blocks.py:316
def validate(self, context)
Definition: blocks.py:347
def validate(self, context)
Definition: blocks.py:419
def __init__(self, header, body=None, end=None, errors=())
Definition: blocks.py:401
NodeVisitor that supports matching nodes based on their base classes.
Definition: visitor.py:45
def write(msg, level='INFO', html=False)
Writes the message to the log file using the given level.
Definition: logger.py:84
def file_writer(path=None, encoding='UTF-8', newline=None, usage=None)
Definition: robotio.py:25