Robot Framework Integrated Development Environment (RIDE)
ctrlcommands.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 os
17 import re
18 import time
19 from itertools import chain
20 
21 # from .filecontrollers import ResourceFileController
22 # ResourceFileController
23 # from . import macrocontrollers
24 # from .macrocontrollers import KeywordNameController, ForLoopStepController, TestCaseController
25 # from mercurial.match import predicatematcher
26 
27 from . import settingcontrollers
28 # import _SettingController, VariableController
29 # from . import tablecontrollers
30 # import VariableTableController
31 from . import validators
32 # import BaseNameValidator
33 from ..namespace.embeddedargs import EmbeddedArgsHandler
34 from ..namespace import namespace
35 # from ..namespace.namespace import _VariableStash
36 from ..publish.messages import (RideSelectResource, RideFileNameChanged, RideSaving, RideSaved, RideSaveAll,
37  RideExcludesChanged)
38 from ..utils import overrides, variablematcher
39 
40 
41 class Occurrence():
42 
43  def __init__(self, item, value):
44  self._item_item = item
45  self._value_value = value
46  self._replaced_replaced = False
47  self.countcount = 1
48 
49  def __eq__(self, other):
50  if not isinstance(other, Occurrence):
51  return False
52  return (self.parentparentparent is other.parent and
53  self._in_steps_in_steps() and other._in_steps())
54 
55  @property
56  item = property
57 
58  def item(self):
59  return self._item_item
60 
61  @property
62  source = property
63 
64  def source(self):
65  return self.datafiledatafiledatafile.source
66 
67  @property
68  datafile = property
69 
70  def datafile(self):
71  return self._item_item.datafile
72 
73  @property
74  parent = property
75 
76  def parent(self):
77  if self._in_for_loop_in_for_loop():
78  return self._item_item.parent.parent
79  return self._item_item.parent
80 
81  @property
82  location = property
83 
84  def location(self):
85  return self._item_item.parent.name
86 
87  @property
88  usage = property
89 
90  def usage(self):
91  if self._in_variable_table_in_variable_table():
92  return "Variable Table"
93  elif self._in_settings_in_settings():
94  return self._item_item.label
95  elif self._in_kw_name_in_kw_name():
96  return 'Keyword Name'
97  return 'Steps' if self.countcount == 1 else 'Steps (%d usages)' % self.countcount
98 
99  def _in_settings(self):
100  return isinstance(self._item_item, settingcontrollers._SettingController)
101 
103  from . import tablecontrollers
104  return isinstance(self._item_item, tablecontrollers.VariableTableController)
105 
106  def _in_kw_name(self):
107  from .macrocontrollers import KeywordNameController
108 
109  return isinstance(self._item_item, KeywordNameController)
110 
111  def _in_steps(self):
112  return not (self._in_settings_in_settings() or self._in_kw_name_in_kw_name())
113 
114  def _in_for_loop(self):
115  from .macrocontrollers import ForLoopStepController
116 
117  return isinstance(self._item_item.parent, ForLoopStepController)
118 
119  def replace_keyword(self, new_name):
120  # print(f"DEBUG: ctrlcommands.py replace_keyword new_name={new_name}")
121  self._item_item.replace_keyword(*self._get_replace_values_get_replace_values(new_name))
122  self._replaced_replaced = not self._replaced_replaced
123 
124  def _get_replace_values(self, new_name):
125  if self._replaced_replaced:
126  return self._value_value, new_name
127  return new_name, self._value_value
128 
130  self._item_item.notify_value_changed()
131 
132 
133 class _Command():
134 
135  modifying = True
136 
137  def execute(self, context):
138  raise NotImplementedError(self.__class__)
139 
140  def __str__(self):
141  return '%s(%s)' % (self.__class__.__name__, self._params_str_params_str())
142 
143  def _params_str(self):
144  return ', '.join(self._format_param_format_param(p) for p in self._params_params())
145 
146  @staticmethod
147  def _format_param(param):
148  if isinstance(param, str):
149  return '"%s"' % param
150  return str(param)
151 
152  def _params(self):
153  return []
154 
155 
157 
158  def __init__(self, new_name):
159  self._new_name_new_name = new_name
160 
161  def execute(self, context):
162  context.copy(self._new_name_new_name)
163 
164  def _params(self):
165  return [self._new_name_new_name]
166 
167 
169 
170  def __init__(self, tag, value):
171  self._tag_tag = tag
172  self._value_value = value.strip()
173 
174  def _params(self):
175  return (self._tag_tag, self._value_value)
176 
177  def execute(self, context):
178  tags = [tag for tag in context if tag.controller == context]
179  context.set_value(self._create_value_create_value(tags))
180  context.notify_value_changed()
181 
182  def _create_value(self, old_values):
183  if old_values == [] and self._tag_tag.is_empty():
184  return self._value_value
185  return ' | '.join(value for value in
186  self._create_value_list_create_value_list(old_values)
187  if value != '')
188 
189  def _create_value_list(self, old_values):
190  if self._tag_tag.is_empty():
191  return [v.name for v in old_values] + [self._value_value]
192  else:
193  new_list = []
194  for v in old_values:
195  if v != self._tag_tag:
196  new_list.append(v.name)
197  if self._value_value not in new_list:
198  new_list += [self._value_value]
199  return new_list
200 
201 
203 
204  def execute(self, tag):
205  tag.delete()
206  tag.controller.notify_value_changed()
207 
208 
210 
211  def execute(self, context):
212  result = self._execute_without_redo_clear_execute_without_redo_clear(context)
213  context.clear_redo()
214  return result
215 
216  def _execute_without_redo_clear(self, context):
217  result = self._execute(context)
218  context.push_to_undo(self._get_undo_command_get_undo_command_get_undo_command())
219  return result
220 
221  @property
222  _get_undo_command = property
223 
224  def _get_undo_command(self):
225  raise NotImplementedError(self.__class__.__name__)
226 
227 
228 class Undo(_Command):
229 
230  def execute(self, context):
231  if not context.is_undo_empty():
232  result = context.pop_from_undo()._execute_without_redo_clear(context)
233  redo_command = context.pop_from_undo()
234  context.push_to_redo(redo_command)
235  return result
236 
237 
238 class Redo(_Command):
239 
240  def execute(self, context):
241  if not context.is_redo_empty():
242  return context.pop_from_redo()._execute_without_redo_clear(context)
243 
244 
246 
247  def __init__(self, destination):
248  self._destination_destination = destination
249 
250  def _params(self):
251  return [self._destination_destination]
252 
253  def execute(self, context):
254  context.delete()
255  self._destination_destination.add_test_or_keyword(context)
256 
257 
259 
260  def __init__(self, path):
261  self._path_path = path
262 
263  def execute(self, context):
264  res = context.new_resource(self._path_path)
265  RideSelectResource(item=res).publish()
266  return res
267 
268 
270 
271  def __init__(self, datafile):
272  self._datafile_datafile = datafile
273 
274  def execute(self, context):
275  context.mark_dirty()
276  context.set_datafile(self._datafile_datafile)
277 
278 
280 
281  def _execute(self, context):
282  if self.change_stepschange_steps(context):
283  context.notify_steps_changed()
284  return True
285  return False
286 
287 
288  def change_steps(self, context):
289  raise NotImplementedError(self.__class__.__name__)
290 
291  def _step(self, context):
292  try:
293  return context.steps[self._row]
294  except IndexError:
295  return NonExistingStep()
296 
297 
299  def __getattr__(self, name):
300  return lambda *args: ''
301 
302 
303 class NullObserver():
304  notify = finish = lambda x: None
305 
306 
308 
309 
312  _gherkin_prefix = re.compile('^(Given|When|Then|And|But) ', re.IGNORECASE)
313 
314  def __init__(self, original_name, new_name, observer, keyword_info=None):
315  self._original_name, self._new_name_new_name = self._check_gherkin_check_gherkin(new_name,
316  original_name
317  )
318  # print(f"DEBUG: ctrlcommands.py RenameKeywordOccurrences ENTER after check_gherkin\n"
319  # f"self._original_name={self._original_name} self._new_name={self._new_name} ")
320  self._observer_observer = observer
321  self._keyword_info_keyword_info = keyword_info
322  self._occurrences_occurrences = None
323 
324  def _check_gherkin(self, new_name, original_name):
325  was_gherkin, keyword_name = self._get_gherkin_get_gherkin(original_name)
326  is_gherkin, new_keyword_name = self._get_gherkin_get_gherkin(new_name)
327  if was_gherkin and not is_gherkin:
328  keyword_name = original_name
329  if not was_gherkin and is_gherkin:
330  # When we change non-gherkin to gherkin, the keyword changes too.
331  # The workaround is not to Rename keyword, but only edit field.
332  new_keyword_name = new_name
333  if was_gherkin and is_gherkin:
334  # Check if the first word has changed
335  if original_name.split(' ', 1)[0].lower() != new_name.split(
336  ' ', 1)[0].lower():
337  new_keyword_name = new_name
338  keyword_name = original_name
339  return keyword_name, new_keyword_name
340 
341  def _get_gherkin(self, original_name):
342  keyword_value = re.sub(self._gherkin_prefix_gherkin_prefix, '', original_name)
343  value_is_gherkin = (keyword_value != original_name)
344  return value_is_gherkin, keyword_value
345 
346  def _params(self):
347  return (self._original_name, self._new_name_new_name,
348  self._observer_observer, self._keyword_info_keyword_info)
349 
350  def _execute(self, context):
351  self._observer_observer.notify()
352  self._occurrences_occurrences = \
353  self._find_occurrences_find_occurrences(context) if self._occurrences_occurrences is None \
354  else self._occurrences_occurrences
355  # print(f"DEBUG: ctrlcommands.py RenameKeywordOccurrences ENTER execute\n"
356  # f"self._params={self._params()}\n"
357  # f"self._occurrences={self._occurrences}")
358  self._replace_keywords_in_replace_keywords_in(self._occurrences_occurrences)
359  context.update_namespace()
360  self._notify_values_changed_notify_values_changed(self._occurrences_occurrences)
361  self._observer_observer.finish()
362 
363  def _find_occurrences(self, context):
364  occurrences = []
365  for occ in context.execute(FindOccurrences(
366  self._original_name, keyword_info=self._keyword_info_keyword_info)):
367  self._observer_observer.notify()
368  occurrences.append(occ)
369  self._observer_observer.notify()
370  return occurrences
371 
372  def _replace_keywords_in(self, occurrences):
373  for oc in occurrences:
374  oc.replace_keyword(self._new_name_new_name)
375  self._observer_observer.notify()
376 
377  def _notify_values_changed(self, occurrences):
378  for oc in occurrences:
379  oc.notify_value_changed()
380  self._observer_observer.notify()
381 
382  def _get_undo_command(self):
383  self._observer_observer = NullObserver()
384  return self
385 
386 
388 
389  def __init__(self, new_name):
390  self._new_name_new_name = new_name
391 
392  def _params(self):
393  return self._new_name_new_name
394 
395  def _execute(self, context):
396  old_name = context.name
397  context.test_name.rename(self._new_name_new_name)
398  context.test_name._item.notify_name_changed(old_name)
399 
400  def _get_undo_command(self):
401  return self
402 
403 
405 
406  def __init__(self, new_basename):
407  self._new_basename_new_basename = new_basename
408  self._validator_validator = validators.BaseNameValidator(new_basename)
409 
410  def execute(self, context):
411  validation_result = self._validator_validator.validate(context)
412  if validation_result:
413  old_filename = context.filename
414  context.set_basename(self._new_basename_new_basename.strip())
415  RideFileNameChanged(datafile=context,
416  old_filename=old_filename).publish()
417  return validation_result
418 
419 
421 
422  def execute(self, excluded_controller):
423  directory_controller = excluded_controller.remove_from_excludes()
424  RideExcludesChanged(old_controller=excluded_controller,
425  new_controller=directory_controller).publish()
426 
427 
429 
430  def execute(self, directory_controller):
431  excluded_controller = directory_controller.exclude()
432  RideExcludesChanged(old_controller=directory_controller,
433  new_controller=excluded_controller).publish()
434 
435 
437 
438  def __init__(self, new_basename, get_should_modify_imports):
439  self._new_basename_new_basename = new_basename
440  self._should_modify_imports_should_modify_imports = get_should_modify_imports
441 
442  def execute(self, context):
443  validation_result = validators.BaseNameValidator(
444  self._new_basename_new_basename).validate(context)
445  if validation_result:
446  old_filename = context.filename
447  modify_imports = self._should_modify_imports_should_modify_imports()
448  if modify_imports is None:
449  return
450  if modify_imports:
451  context.set_basename_and_modify_imports(self._new_basename_new_basename)
452  else:
453  context.set_basename(self._new_basename_new_basename)
454  RideFileNameChanged(datafile=context,
455  old_filename=old_filename).publish()
456  return validation_result
457 
458 
460  index_difference = None
461 
462  def _execute(self, context):
463  index_difference = context.sort_tests()
464  self._undo_command_undo_command = RestoreTestOrder(index_difference)
465 
466  def _get_undo_command(self):
467  return self._undo_command_undo_command
468 
469 
471  index_difference = None
472 
473  def _execute(self, context):
474  index_difference = context.sort_keywords()
475  self._undo_command_undo_command = RestoreKeywordOrder(index_difference)
476 
477  def _get_undo_command(self):
478  return self._undo_command_undo_command
479 
480 
482  index_difference = None
483 
484  def _execute(self, context):
485  index_difference = context.sort_variables()
486  self._undo_command_undo_command = RestoreVariableOrder(index_difference)
487 
488  def _get_undo_command(self):
489  return self._undo_command_undo_command
490 
491 
493 
494  def __init__(self, index_difference):
495  self._index_difference_index_difference = index_difference
496 
497  def _execute(self, context):
498  context.restore_test_order(self._index_difference_index_difference)
499 
500  def _get_undo_command(self):
501  return SortTests()
502 
503 
505 
506  def __init__(self, index_difference):
507  self._index_difference_index_difference = index_difference
508 
509  def _execute(self, context):
510  context.restore_keyword_order(self._index_difference_index_difference)
511 
512  def _get_undo_command(self):
513  return SortKeywords()
514 
515 
517 
518  def __init__(self, index_difference):
519  self._index_difference_index_difference = index_difference
520 
521  def _execute(self, context):
522  context.restore_variable_order(self._index_difference_index_difference)
523 
524  def _get_undo_command(self):
525  return SortVariables()
526 
527 
529 
530  def __init__(self, item):
531  self._item_item = item
532 
533 
535 
536  def execute(self, context):
537  context.editable_value = self._item_item
538 
539 
541 
542  def execute(self, context):
543  context.move_up(self._item_item)
544 
545 
547 
548  def execute(self, context):
549  context.move_down(self._item_item)
550 
551 
553 
554  def execute(self, context):
555  context.delete(self._item_item)
556 
557 
559 
560  def execute(self, context):
561  context.clear()
562 
563 
565 
566  def execute(self, context):
567  context.remove_from_filesystem()
568  context.remove()
569 
570 
572  modifying = False
573 
574  def execute(self, context):
575  context.open_filemanager()
576 
577 
579 
580  def execute(self, context):
581  context.remove_readonly()
582 
583 
585 
586  def execute(self, context):
587  context.remove_folder_from_filesystem()
588  context.remove_from_model()
589 
590 
592 
593  def __init__(self, values, comment):
594  self._values_values = values
595  self._comment_comment = comment
596 
597  def execute(self, context):
598  context.set_value(*self._values_values)
599  context.set_comment(self._comment_comment)
600 
601 
603 
604  def __init__(self, values, comment):
605  self._values_values = values
606  self._comment_comment = comment
607 
608  def execute(self, context):
609  lib = context.add_library(*self._values_values)
610  lib.set_comment(self._comment_comment)
611  return lib
612 
613 
615 
616  def __init__(self, values, comment):
617  self._values_values = values
618  self._comment_comment = comment
619 
620  def execute(self, context):
621  res = context.add_resource(*self._values_values)
622  res.set_comment(self._comment_comment)
623  return res
624 
625 
627 
628  def __init__(self, values, comment):
629  self._values_values = values
630  self._comment_comment = comment
631 
632  def execute(self, context):
633  var = context.add_variables(*self._values_values)
634  var.set_comment(self._comment_comment)
635  return var
636 
637 
639 
640  def execute(self, context):
641  context.remove_static_imports_to_this()
642  DeleteFile.execute(self, context)
643 
644 
646 
647  def execute(self, context):
648  context.remove_static_imports_to_this()
649  DeleteFolder.execute(self, context)
650 
651 
653 
654  def __init__(self, new_name, new_value, new_comment):
655  self._new_name_new_name = new_name
656  self._new_value_new_value = new_value
657  self._new_comment_new_comment = new_comment
658 
659  def execute(self, context):
660  has_data = context.has_data()
661  context.set_value(self._new_name_new_name, self._new_value_new_value)
662  context.set_comment(self._new_comment_new_comment)
663  if has_data:
664  context.notify_value_changed()
665  else:
666  context.notify_variable_added()
667 
668 
670 
671  def __init__(self, new_name):
672  self._new_name_new_name = new_name
673 
674  def execute(self, context):
675  context.execute(UpdateVariable(self._new_name_new_name, context.value,
676  context.comment))
677 
678 
680  modifying = False
681 
682  def __init__(self, keyword_name, keyword_info=None):
683  if keyword_name.strip() == '':
684  raise ValueError('Keyword name can not be "%s"' % keyword_name)
685  self._keyword_name_keyword_name = keyword_name
686  self._keyword_info_keyword_info = keyword_info
687  self._keyword_regexp_keyword_regexp = self._create_regexp_create_regexp(keyword_name)
688 
689  def _create_regexp(self, keyword_name):
690  if variablematcher.contains_scalar_variable(keyword_name) and \
691  not variablematcher.is_variable(keyword_name):
692  kw = lambda: 0
693  kw.arguments = None
694  kw.name = keyword_name
695  return EmbeddedArgsHandler(kw).name_regexp
696 
697  def execute(self, context):
698  self._keyword_source_keyword_source = \
699  self._keyword_info_keyword_info and self._keyword_info_keyword_info.source or \
700  self._find_keyword_source_find_keyword_source(context.datafile_controller)
701  return self._find_occurrences_in_find_occurrences_in(self._items_from_items_from(context))
702 
703  def _items_from(self, context):
704  for df in context.datafiles:
705  self._yield_for_other_threads_yield_for_other_threads()
706  if self._items_from_datafile_should_be_checked_items_from_datafile_should_be_checked(df):
707  for item in self._items_from_datafile_items_from_datafile(df):
708  yield item
709 
711  if datafile.filename and \
712  os.path.basename(datafile.filename) == self._keyword_source_keyword_source:
713  return True
714  return self._find_keyword_source_find_keyword_source(datafile) == self._keyword_source_keyword_source
715 
716  def _items_from_datafile(self, df):
717  for setting in df.settings:
718  yield setting
719  for test_items in (self._items_from_test_items_from_test(test) for test in df.tests):
720  for item in test_items:
721  yield item
722  for kw_items in (self._items_from_keyword_items_from_keyword(kw) for kw in df.keywords):
723  for item in kw_items:
724  yield item
725 
726  def _items_from_keyword(self, kw):
727  return chain([kw.keyword_name] if kw.source == self._keyword_source_keyword_source
728  else [], kw.steps, [kw.teardown] if kw.teardown else [])
729 
730  @staticmethod
731  def _items_from_test(test):
732  return chain(test.settings, test.steps)
733 
734  def _find_keyword_source(self, datafile_controller):
735  item_info = datafile_controller.keyword_info(self._keyword_name_keyword_name)
736  return item_info.source if item_info else None
737 
738  def _find_occurrences_in(self, items):
739  return (Occurrence(item, self._keyword_name_keyword_name) for item in items
740  if self._contains_item_contains_item(item))
741 
742  def _contains_item(self, item):
743  self._yield_for_other_threads_yield_for_other_threads()
744  return item.contains_keyword(
745  self._keyword_regexp_keyword_regexp or self._keyword_name_keyword_name)
746 
747  @staticmethod
749  # GIL !?#!!!
750  # THIS IS TO ENSURE THAT OTHER THREADS WILL GET SOME SPACE ALSO
751  time.sleep(0)
752 
753 
755 
756  @overrides(FindOccurrences)
757  def _contains_item(self, item):
758  self._yield_for_other_threads_yield_for_other_threads()
759  return item.contains_variable(self._keyword_name_keyword_name)
760 
761  def _items_from_datafile(self, df):
762  for itm in FindOccurrences._items_from_datafile(self, df):
763  yield itm
764  yield df.variables
765 
766  def _items_from_controller(self, ctrl):
767  from .macrocontrollers import TestCaseController
768 
769  if isinstance(ctrl, TestCaseController):
770  return self._items_from_test_items_from_test(ctrl)
771  else:
772  return self._items_from_keyword_items_from_keyword_items_from_keyword(ctrl)
773 
774  def _items_from_keyword(self, kw):
775  return chain([kw.keyword_name], kw.steps, kw.settings)
776 
777  def _items_from(self, context):
778  self._context_context = context
779  if self._is_local_variable_is_local_variable(self._keyword_name_keyword_name, context):
780  for item in self._items_from_controller_items_from_controller(context):
781  yield item
782  else:
783  for df in context.datafiles:
784  self._yield_for_other_threads_yield_for_other_threads()
785  if self._items_from_datafile_should_be_checked_items_from_datafile_should_be_checked_items_from_datafile_should_be_checked(df):
786  for item in self._items_from_datafile_items_from_datafile_items_from_datafile(df):
787  yield item
788 
790  if self._is_file_variable_is_file_variable(self._keyword_name_keyword_name, self._context_context):
791  return datafile in [self._context_context.datafile_controller] + \
792  self._get_all_where_used_get_all_where_used(self._context_context)
793  elif self._is_imported_variable_is_imported_variable(self._keyword_name_keyword_name, self._context_context):
794  return datafile in [self._get_source_of_imported_var_get_source_of_imported_var(
795  self._keyword_name_keyword_name, self._context_context)] + \
796  self._get_all_where_used_get_all_where_used(self._get_source_of_imported_var_get_source_of_imported_var(
797  self._keyword_name_keyword_name, self._context_context))
798  else:
799  return True
800 
801  @staticmethod
802  def _is_local_variable(name, context):
803  if isinstance(context, settingcontrollers.VariableController):
804  return False
805  return name in context.get_local_variables() or \
806  any(step.contains_variable_assignment(name)
807  for step in context.steps)
808 
809  @staticmethod
810  def _is_file_variable(name, context):
811  return context.datafile_controller.variables.contains_variable(name)
812 
813  def _is_imported_variable(self, name, context):
814  return self._get_source_of_imported_var_get_source_of_imported_var(name, context) not in \
815  [None, context.datafile_controller]
816 
817  @staticmethod
819  return name in list(namespace._VariableStash.global_variables.keys())
820 
821  def _get_source_of_imported_var(self, name, context):
822  for df in self._get_all_imported_get_all_imported(context):
823  if df.variables.contains_variable(name):
824  return df
825  return None
826 
827  @staticmethod
828  def _get_all_imported(context):
829  files = [context.datafile_controller]
830  for f in files:
831  files += [imp.get_imported_controller()
832  for imp in f.imports if imp.is_resource and
833  imp.get_imported_controller() not in files]
834  return files
835 
836  @staticmethod
837  def _get_all_where_used(context):
838  from .filecontrollers import ResourceFileController
839 
840  files = [context.datafile_controller]
841  for f in files:
842  if isinstance(f, ResourceFileController):
843  files += [imp.datafile_controller
844  for imp in f.get_where_used()]
845  return files
846 
847 
849  if not cells:
850  raise ValueError('Keyword can not be empty')
851  while cells[0] == '':
852  cells.pop(0)
853  name = cells[0]
854  args = cells[1:]
855  argstr = ' | '.join(('${arg%s}' % (i + 1) for i in range(len(args))))
856  return AddKeyword(name, argstr)
857 
858 
860 
861  def __init__(self, new_kw_name, args=None):
862  self._kw_name_kw_name = new_kw_name
863  self._args_args = args or []
864 
865  def _execute(self, context):
866  kw = context.create_keyword(self._kw_name_kw_name, self._args_args)
867  self._undo_command_undo_command = RemoveMacro(kw)
868  return kw
869 
870  def _get_undo_command(self):
871  return self._undo_command_undo_command
872 
873 
875 
876  def __init__(self, new_test_name):
877  self._test_name_test_name = new_test_name
878 
879  def execute(self, context):
880  return context.create_test(self._test_name_test_name)
881 
882 
884 
885  def __init__(self, path):
886  self._path_path = path
887 
888  def execute(self, context):
889  ctrl = self._add_data_file_add_data_file(context)
890  context.notify_suite_added(ctrl)
891  return ctrl
892 
893  def _add_data_file(self, context):
894  raise NotImplementedError(self.__class__.__name__)
895 
896 
898 
899  def _add_data_file(self, context):
900  return context.new_test_case_file(self._path_path)
901 
902 
904 
905  def _add_data_file(self, context):
906  return context.new_test_data_directory(self._path_path)
907 
908 
910 
911  def __init__(self, path):
912  self._path_path = path
913 
914  def execute(self, context):
915  context.new_file_project(self._path_path)
916 
917 
919 
920  def __init__(self, path):
921  self._path_path = path
922 
923  def execute(self, context):
924  context.new_directory_project(self._path_path)
925 
926 
928 
929  def __init__(self, format):
930  self._format_format = format
931 
932  def execute(self, context):
933  context.save_with_new_format(self._format_format)
934 
935 
937 
938  def __init__(self, format):
939  self._format_format = format
940 
941  def execute(self, context):
942  context.save_with_new_format_recursive(self._format_format)
943 
944 
946 
947  def __init__(self, var_controller):
948  self._var_controller_var_controller = var_controller
949  self._undo_command_undo_command = AddVariable(var_controller.name,
950  var_controller.value,
951  var_controller.comment)
952 
953  def _execute(self, context):
954  context.datafile_controller.\
955  variables.remove_var(self._var_controller_var_controller)
956 
957  def _get_undo_command(self):
958  return self._undo_command_undo_command
959 
960 
962 
963  def __init__(self, name, value, comment):
964  self._name_name = name
965  self._value_value = value
966  self._comment_comment = comment
967 
968  def _execute(self, context):
969  var_controller = context.datafile_controller.\
970  variables.add_variable(self._name_name, self._value_value, self._comment_comment)
971  self._undo_command_undo_command = RemoveVariable(var_controller)
972  return var_controller
973 
974  def _get_undo_command(self):
975  return self._undo_command_undo_command
976 
977  def __str__(self):
978  return 'AddVariable("%s", "%s", "%s")' % \
979  (self._name_name, self._value_value, self._comment_comment)
980 
981 
983 
984  def __init__(self, user_script):
985  self._user_script_user_script = user_script
986 
987  def _execute(self, context):
988  self._user_script_user_script.recreate()
989 
990  def _get_undo_command(self):
991  return RemoveMacro(self._user_script_user_script)
992 
993 
995 
996  def __init__(self, item):
997  self._item_item = item
998 
999  def _execute(self, context):
1000  self._item_item.delete()
1001 
1003  return RecreateMacro(self._item_item)
1004 
1005 
1007 
1008  def __init__(self, new_kw_name, new_kw_args, step_range):
1009  self._name_name = new_kw_name
1010  self._args_args = new_kw_args
1011  self._rows_rows = step_range
1012 
1013  def _params(self):
1014  return (self._name_name, self._args_args, self._rows_rows)
1015 
1016  def execute(self, context):
1017  context.extract_keyword(self._name_name, self._args_args, self._rows_rows)
1018  context.notify_steps_changed()
1019  context.clear_undo()
1020 
1021 
1022 def ExtractScalar(name, value, comment, cell):
1023  print(f"DEBUG: ctrlcommands.py ExtractScalar name{name} value{value}, comment{comment}, cell{cell}")
1024  return CompositeCommand(AddVariable(name, value, comment),
1025  ChangeCellValue(cell[0], cell[1], name))
1026 
1027 
1028 def ExtractList(name, value, comment, cells):
1029  row, col = cells[0]
1030  return CompositeCommand(AddVariable(name, value, comment),
1031  ChangeCellValue(row, col, name),
1032  DeleteCells(
1033  (row, col + 1), (row, col + len(cells) - 1)))
1034 
1035 
1037 
1038  def __init__(self, row, col, value, insert=False):
1039  self._row_row = row
1040  self._col_col = col
1041  self._value_value = self._escape_newlines_escape_newlines(value)
1042  self._insert_insert = insert
1043 
1044  def change_steps(self, context):
1045  steps = context.steps
1046  while len(steps) <= self._row_row:
1047  context.add_step(len(steps))
1048  steps = context.steps
1049  step = self._step_step(context)
1050  self._undo_command_undo_command = ChangeCellValue(
1051  self._row_row, self._col_col, step.get_value(self._col_col), insert=False)
1052  # print(f"DEBUG: change_steps before change from_column: ({self._row}, {self._col}, {self._value}) Line: {step.as_list()}")
1053  if self._insert_insert:
1054  step.insert_value_before(self._col_col, self._value_value)
1055  # print(f"DEBUG: change_steps after insert cell Line: {context.steps[self._row].as_list()}")
1056  else:
1057  step.change(self._col_col, self._value_value, not self._insert_insert)
1058  # print(f"DEBUG: change_steps after change from_column: ({self._row}, {self._col}, {self._value}) Line: {context.steps[self._row].as_list()}")
1059  self._step_step(context).remove_empty_columns_from_end(not self._insert_insert)
1060  # value = self._step(context).get_value(self._col).strip()
1061  # print(f"DEBUG: change_steps after change from_column: value={value} self.value = {self._value}")
1062  # DEGUG: Next validation is not possible to call when the step is Indented
1063  # assert self._validate_postcondition(context), 'Should have correct value after change'
1064  return True
1065 
1066  @staticmethod
1067  def _escape_newlines(item):
1068  for newline in ('\r\n', '\n', '\r'):
1069  item = item.replace(newline, '\\n')
1070  return item
1071 
1072  def _validate_postcondition(self, context):
1073  value = self._step_step(context).get_value(self._col_col).strip()
1074  should_be = self._value_value.strip()
1075  # print(f"DEBUG: change_steps _validate_postcondition: value={value} should_be={should_be}")
1076  if value == should_be:
1077  return True
1078  return (value.replace(' ', '') == 'FOR' and
1079  should_be.replace(' ', '') == 'FOR') or\
1080  (value.replace(' ', '') == 'FOR' and
1081  should_be.replace(' ', '').upper() == ':FOR') or\
1082  (value.replace(' ', '') == 'END' and
1083  should_be.replace(' ', '') == 'END')
1084 
1086  return self._undo_command_undo_command
1087 
1088  def __str__(self):
1089  return '%s(%s, %s, "%s")' % \
1090  (self.__class__.__name__, self._row_row, self._col_col, self._value_value)
1091 
1092 
1094 
1095  def __init__(self, reformat=False):
1096  self._reformat_reformat = reformat
1097 
1098  def execute(self, context):
1099  RideSaving(path=context.filename, datafile=context).publish()
1100  datafile_controller = context.datafile_controller
1101  if self._reformat_reformat:
1102  for macro_controller in chain(datafile_controller.tests, datafile_controller.keywords):
1103  macro_controller.execute(Purify())
1104  datafile_controller.save()
1105  datafile_controller.unmark_dirty()
1106  RideSaved(path=context.filename).publish()
1107 
1108 
1110 
1111  def __init__(self, reformat=False):
1112  self._reformat_reformat = reformat
1113 
1114  def execute(self, context):
1115  for datafile_controller in context._get_all_dirty_controllers():
1116  if datafile_controller.has_format():
1117  datafile_controller.execute(SaveFile(self._reformat_reformat))
1118  RideSaveAll().publish()
1119 
1120 
1122 
1123  def execute(self, context):
1124  i = 0
1125  while True:
1126  if len(context.steps) <= i:
1127  break
1128  # Steps can change during this operation
1129  # this is why index based iteration - step reference can be stale
1130  step = context.steps[i]
1131  step.remove_empty_columns_from_end()
1132  # print(f"DEBUG: Purify after remove_empty_columns_from_end step={step}")
1133  # DEBUG Purify not changing rmpty columns from begining
1134  # if step.has_only_comment():
1135  # step.remove_empty_columns_from_beginning()
1136  i += 1
1137  # print(f"DEBUG: Purify before DeleteRows")
1138  context.execute(DeleteRows(context.get_empty_rows()))
1139  context.notify_steps_changed()
1140 
1141 
1143 
1144  def __init__(self, row, col):
1145  self._row_row = row
1146  self._col_col = col
1147 
1148  def _params_str(self):
1149  return '%s, %s' % (self._row_row, self._col_col)
1150 
1151  def change_steps(self, context, delete=False):
1152  self._step_step(context).shift_right(self._col_col, delete=delete)
1153  if not delete:
1154  # print(f"DEBUG: InsertCell change_steps row:{self._row} cols:{self._col} row BEFORE _recreate: {context.steps[self._row].as_list()}"
1155  # f"\ntype step={type(self._step(context))}")
1156  self._step_step(context)._recreate(context.steps[self._row_row].as_list())
1157  assert self._step_step(context).get_value(self._col_col) == '', 'Should have an empty value after insert'
1158  return True
1159 
1161  return DeleteCell(self._row_row, self._col_col)
1162 
1163 
1165 
1166  def __init__(self, row, col):
1167  self._row_row = row
1168  self._col_col = col
1169  # print(f"\nDEBUG: DeleteCell init enter coords ({self._row}, {self._col})")
1170 
1171  def _params(self):
1172  return self._row_row, self._col_col
1173 
1174  def change_steps(self, context):
1175  step = self._step_step(context)
1176  # print(f"DEBUG: DeleteCell enter change: {step.as_list()}")
1178  InsertCell(self._row_row, self._col_col),
1179  ChangeCellValue(self._row_row, self._col_col,
1180  step.get_value(self._col_col), insert=False))
1181  step.shift_left(self._col_col, delete=True)
1182  return True
1183 
1185  return self._undo_command_undo_command
1186 
1187 
1189 
1190 
1194  def __init__(self, row):
1195  self._row_row = row
1196 
1197  def change_steps(self, context):
1198  if len(context.steps) <= self._row_row:
1199  return False
1200  self._change_value(context)
1201  return True
1202 
1203  def __str__(self):
1204  return '%s(%s)' % (self.__class__.__name__, self._row_row)
1205 
1206 
1208 
1209  def _change_value(self, context):
1210  step = context.steps[self._row_row]
1211  # print(f"DEBUG: DeleteRow enter change row={self._row}: {step.as_list()}")
1213  AddRow(self._row_row), PasteArea((self._row_row, 0), [step.as_list()]))
1214  context.remove_step(self._row_row)
1215 
1217  if hasattr(self, '_undo_command'):
1218  return self._undo_command_undo_command
1219  return AddRow(self._row_row)
1220 
1221 
1223 
1224  def _change_value(self, context):
1225  row = self._row_row if self._row_row != -1 else len(context.steps)
1226  context.add_step(row)
1227  # print(f"DEBUG: AddRow after adding = {context.steps}")
1228  assert not(any(i for i in self._step_step(context).as_list() if i)), \
1229  'Should have an empty row after add instead %r' % \
1230  self._step_step(context).as_list()
1231 
1233  return DeleteRow(self._row_row)
1234 
1235 
1237 
1238  def _change_value(self, context):
1239  # print(f"DEBUG: enter CommentRow")
1240  self._step_step(context).comment()
1241  return True
1242 
1244  return UncommentRow(self._row_row)
1245 
1246 
1248 
1249  def _change_value(self, context):
1250  self._step_step(context).uncomment()
1251  return True
1252 
1254  return CommentRow(self._row_row)
1255 
1256 
1258 
1259  def __init__(self, rows):
1260  self._rows_rows = rows
1261 
1262  def _params(self):
1263  return [self._rows_rows]
1264 
1265  def change_steps(self, context): # NOTE: Nevermind the quality of this code. Unit tests are passing ;)
1266 
1267  def non_empty_from_left(line):
1268  assert line >= 0
1269  steps = context.steps[line].as_list()
1270  index = 0
1271  while index < len(steps) and steps[index] == '':
1272  index += 1
1273  if index == len(steps):
1274  # print(f"DEBUG: MoveRowsUp non_empty_from_left RETURN -1")
1275  return -1
1276  return index
1277 
1278  if len(self._rows_rows) == 0 or self._last_row_last_row_last_row > len(context.steps) - 1 or \
1279  self._first_row_first_row_first_row == 0:
1280  return False
1281  number_of_steps_before = len(context.steps)
1282  # print(f"DEBUG: MoveRowsUp START")
1283  # for s in context.steps:
1284  # print(f"{s.as_list()}")
1285  # print(f"DEBUG: MoveRowsUp number_of_steps_before: {number_of_steps_before} rows: {self._rows}")
1286  for row in self._rows_rows:
1287  # print(f"DEBUG: MoveRowsUp loop row: {row}\n line to move {context.steps[row].as_list()}")
1288  index = non_empty_from_left(row)
1289  prev_cell_row = row - 1 if row > 0 else 0
1290  prev_cell = non_empty_from_left(prev_cell_row)
1291  pre_prev_row = row - 2 if row > 1 else 0
1292  pre_prev_col = non_empty_from_left(pre_prev_row)
1293  next_cell_row = row + 1 if row + 1 < number_of_steps_before else row
1294  next_row_col = non_empty_from_left(next_cell_row)
1295  add_indent = (pre_prev_col > index and context.steps[row]._step.cells[index] != 'END') or (index < pre_prev_col and context.steps[pre_prev_row]._step.cells[pre_prev_col] == 'FOR')
1296  del_indent = (prev_cell > index) and (context.steps[row]._step.cells[index] == 'END')
1297  del_prev_indent = (prev_cell < index) and (context.steps[prev_cell_row]._step.cells[prev_cell] == 'FOR')
1298  # pre_prev_col > index
1299  if context.steps[row].as_list()[index] == 'FOR':
1300  new_next_indent = (prev_cell == index and (context.steps[prev_cell_row]._step.cells[prev_cell] != 'END' or pre_prev_col > index)) \
1301  or (context.steps[next_cell_row]._step.cells[index] != 'END' and next_row_col < index)
1302  else:
1303  new_next_indent = (pre_prev_col > next_row_col and context.steps[row]._step.cells[index] != 'END') and \
1304  prev_cell < index
1305  keep_indent = (context.steps[row]._step.cells[index] == 'FOR'
1306  and context.steps[prev_cell_row]._step.cells[prev_cell] == 'FOR' and next_row_col > index
1307  ) or (prev_cell > index and context.steps[row]._step.cells[index] == 'END' and context.steps[prev_cell_row]._step.cells[prev_cell] != 'END')
1308  """
1309  print(f"DEBUG: MoveRowsUp loop row({row}) index({index}) {context.steps[row].as_list()[index]} "
1310  f"prev_cell({prev_cell}) {context.steps[prev_cell_row].as_list()[prev_cell]}\n"
1311  f"next_row({next_row_col}) {context.steps[next_cell_row].as_list()[next_row_col]}\n"
1312  f"pre_prev_col({pre_prev_col}) {context.steps[pre_prev_row].as_list()[pre_prev_col]}\n"
1313  f"add_indent({add_indent}) new_next_indent({new_next_indent}) keep_indent({keep_indent})\n"
1314  f"del_indent({del_indent}) del_prev_indent({del_prev_indent})\n")
1315  print(f"DEBUG: MoveRowsUp before move: next_+1= {context.steps[next_cell_row].as_list()}")
1316  """
1317  context.move_step_up(row)
1318  # print(f"DEBUG: MoveRowsUp after move_up inside loop")
1319  # print(f"DEBUG: MoveRowsUp after move: next_+1= {context.steps[next_cell_row].as_list()}")
1320  # for i in context.steps:
1321  # print(f"{i.as_list()}")
1322  # In case indent was added at Stepcontroller
1323  new_index = non_empty_from_left(prev_cell_row)
1324  if add_indent and new_index > index + 1:
1325  # print(f"DEBUG: MoveRowsUp loop add_indent Reduce because already done")
1326  context.steps[prev_cell_row].shift_left(0, delete=True)
1327  continue
1328  # print(f"DEBUG: MoveRowsUp loop row-1 content after:{context.steps[row-1].as_list()} after(row):{context.steps[row].as_list()}")
1329  prev_cell = non_empty_from_left(prev_cell_row)
1330  # print(f"DEBUG: MoveRowsUp loop prev_cell={prev_cell} new_index = {new_index}")
1331  if keep_indent and new_index > index:
1332  # print(f"DEBUG: MoveRowsUp loop keep_indent:{context.steps[prev_cell_row].as_list()}")
1333  for _ in range(index, new_index):
1334  context.steps[prev_cell_row].shift_left(0, delete=True)
1335  if add_indent and (context.steps[prev_cell_row]._step.cells[prev_cell] != 'FOR' and context.steps[row]._step.cells[index] != 'END'):
1336  # print(f"DEBUG: MoveRowsUp loop add_indent:{context.steps[row].as_list()}")
1337  context.steps[row].shift_right(0)
1338  # context.steps[row].shift_right(0)
1339  if new_next_indent and not keep_indent and not del_indent:
1340  # print(f"DEBUG: MoveRowsUp loop new_next_indent:{context.steps[row].as_list()}")
1341  # context.steps[next_cell_row].shift_right(0)
1342  context.steps[row].shift_right(0)
1343  if add_indent and new_next_indent and context.steps[prev_cell_row]._step.cells[pre_prev_col] != 'FOR':
1344  # print(f"DEBUG: MoveRowsUp loop add_indent and new_next_indent:{context.steps[prev_cell_row].as_list()[pre_prev_col]}")
1345  context.steps[prev_cell_row].shift_right(0)
1346  if del_indent or (keep_indent and context.steps[prev_cell_row]._step.cells[prev_cell] == 'END'):
1347  # print(f"DEBUG: MoveRowsUp loop del_indent:{context.steps[row].as_list()}")
1348  context.steps[row].shift_left(0, delete=True)
1349  if del_prev_indent:
1350  # print(f"DEBUG: MoveRowsUp loop del_prev_indent:{context.steps[prev_cell_row].as_list()}")
1351  context.steps[prev_cell_row].shift_left(0, delete=True)
1352  if keep_indent and prev_cell > index and len(context.steps[prev_cell_row]._step.cells) > prev_cell and context.steps[prev_cell_row]._step.cells[prev_cell] == 'END':
1353  # print(f"DEBUG: MoveRowsUp loop keep_indent and cell was END:{context.steps[prev_cell_row].as_list()}")
1354  context.steps[prev_cell_row].shift_left(0, delete=True)
1355  # print(f"DEBUG: MoveRowsUp END")
1356  # for s in context.steps:
1357  # print(f"{s.as_list()}")
1358  assert len(context.steps) == number_of_steps_before
1359  return True
1360 
1361  @property
1362  _last_row = property
1363 
1364  def _last_row(self):
1365  return self._rows_rows[-1]
1366 
1367  @property
1368  _first_row = property
1369 
1370  def _first_row(self):
1371  return self._rows_rows[0]
1372 
1374  return MoveRowsDown([r - 1 for r in self._rows_rows if r > 0])
1375 
1376 
1378 
1379  def __init__(self, rows):
1380  self._rows_rows = rows
1381  self._context_context = []
1382 
1383  def _params(self):
1384  return [self._rows_rows]
1385 
1386  def change_steps(self, context): # NOTE: Nevermind the quality of this code. Unit tests are passing ;)
1387  if len(self._rows_rows) == 0 or self._last_row_last_row_last_row >= len(context.steps) - 1:
1388  return False
1389  number_of_steps_before = len(context.steps)
1390  for row in reversed(self._rows_rows):
1391  moving_start = context.steps[row]._first_non_empty_cell()
1392  existing_start = context.steps[row+1]._first_non_empty_cell()
1393  keep_indent = moving_start == existing_start
1394  decrease_indent = moving_start > existing_start
1395  # TODO: Add protection IndexError when moving down on empty lines
1396  increase_indent = moving_start < existing_start or (context.steps[row].as_list()[moving_start] == 'END' and context.steps[row+1].as_list()[existing_start] != 'FOR')
1397  prev_decrease_indent = context.steps[row].as_list()[moving_start] == 'FOR' and increase_indent
1398  if prev_decrease_indent:
1399  increase_indent = False
1400  if context.steps[row+1].as_list()[existing_start] == 'FOR' and context.steps[row].as_list()[moving_start] != 'END' and keep_indent:
1401  increase_indent = True
1402  keep_indent = False
1403  if context.steps[row + 1].as_list()[existing_start] == 'END' and not keep_indent\
1404  or (not increase_indent and context.steps[row].as_list()[moving_start] == 'FOR'):
1405  decrease_indent = True
1406  context.move_step_down(row)
1407  # print(f"DEBUG: MoveRowsDown after move: {[context.steps[r].as_list() for r in range(len(context.steps))]}")
1408  new_existing_start = context.steps[row]._first_non_empty_cell()
1409  if decrease_indent:
1410  # print(f"DEBUG: MoveRowsDown before decrease row: {row+1} {context.steps[row+1].as_list()}")
1411  context.steps[row+1].shift_left(moving_start, )
1412  if increase_indent and not keep_indent:
1413  context.steps[row+1].shift_right(0)
1414  if increase_indent and keep_indent:
1415  context.steps[row].shift_right(0)
1416  if prev_decrease_indent and new_existing_start > moving_start:
1417  # print(f"DEBUG: MoveRowsDown before prev decrease row: {row} {context.steps[row].as_list()}")
1418  context.steps[row].shift_left(moving_start)
1419  assert len(context.steps) == number_of_steps_before
1420  return True
1421 
1422  @property
1423  _last_row = property
1424 
1425  def _last_row(self):
1426  return self._rows_rows[-1]
1427 
1429  return MoveRowsUp([r + 1 for r in self._rows_rows])
1430 
1431 
1433 
1434  def __init__(self, *commands):
1435  self._commands_commands = commands
1436 
1437  def _execute(self, context):
1438  executions = self._executions_executions(context)
1439  undos = [undo for _, undo in executions]
1440  undos.reverse()
1441  self._undo_command_undo_command = self._create_undo_command_create_undo_command(undos)
1442  return [result for result, _ in executions]
1443 
1445  return self._undo_command_undo_command
1446 
1447  def _create_undo_command(self, undos):
1448  return CompositeCommand(*undos)
1449 
1450  def _executions(self, context):
1451  return [(cmd._execute(context), cmd._get_undo_command())
1452  for cmd in self._commands_commands]
1453 
1454 
1456 
1457  def __init__(self, *commands):
1458  self._commands_commands_commands = commands
1459 
1460  def change_steps(self, context):
1461  return any(changed
1462  for changed in CompositeCommand._execute(self, context))
1463 
1465  return self._undo_command_undo_command
1466 
1467  def _create_undo_command(self, undos):
1468  return StepsChangingCompositeCommand(*undos)
1469 
1470  def _executions(self, context):
1471  return [(cmd.change_steps(context), cmd._get_undo_command())
1472  for cmd in self._commands_commands_commands]
1473 
1474 
1475 def DeleteRows(rows):
1477  for r in reversed(sorted(rows))])
1478 
1479 
1480 def AddRows(rows):
1481  # TODO: Refactor to use AddRows(_StepsChangingCommand) command
1482  first_row = sorted(rows)[0]
1483  return StepsChangingCompositeCommand(*[AddRow(first_row) for _ in rows])
1484 
1485 
1486 def CommentRows(rows):
1487  return StepsChangingCompositeCommand(*[CommentRow(r) for r in rows])
1488 
1489 
1490 def UncommentRows(rows):
1491  return StepsChangingCompositeCommand(*[UncommentRow(r) for r in rows])
1492 
1493 
1494 def ClearArea(top_left, bottom_right):
1495  row_s, col_s = top_left
1496  row_e, col_e = bottom_right
1498  *[ChangeCellValue(row, col, '')
1499  for row in range(row_s, row_e + 1)
1500  for col in range(col_s, col_e + 1)])
1501 
1502 
1503 def PasteArea(top_left, content):
1504  row_s, col_s = top_left
1506  *[ChangeCellValue(row + row_s, col + col_s, content[row][col])
1507  for row in range(len(content))
1508  for col in range(len(content[row]))])
1509 
1510 
1511 def InsertArea(top_left, content):
1512  row, _ = top_left
1514  AddRows([row+i for i in range(len(content))]),
1515  PasteArea(top_left, content))
1516 
1517 
1518 def _rows_from_selection(selection):
1519  res = []
1520  for row, col in selection:
1521  if row not in res:
1522  res += [row]
1523  return res
1524 
1525 
1526 def _cols_from_selection(selection):
1527  res = []
1528  for row, col in selection:
1529  if col not in res:
1530  res += [col]
1531  return res
1532 
1533 
1534 def InsertCells(top_left, bottom_right):
1535  row_s, col_s = top_left
1536  row_e, col_e = bottom_right
1538  *[InsertCell(row, col)
1539  for row in range(row_s, row_e + 1)
1540  for col in range(col_s, col_e + 1)])
1541 
1542 
1543 def DeleteCells(top_left, bottom_right):
1544  row_s, col_s = top_left
1545  row_e, col_e = bottom_right
1547  *[DeleteCell(row, col_s)
1548  for row in range(row_s, row_e + 1)
1549  for _ in range(col_s, col_e + 1)])
def __init__(self, new_kw_name, args=None)
def __init__(self, name, value, comment)
def __init__(self, row, col, value, insert=False)
def change_steps(self, context)
Return True if steps changed, False otherwise.
def change_steps(self, context)
Return True if steps changed, False otherwise.
def execute(self, directory_controller)
def __init__(self, new_kw_name, new_kw_args, step_range)
def _find_keyword_source(self, datafile_controller)
def __init__(self, keyword_name, keyword_info=None)
def execute(self, excluded_controller)
def change_steps(self, context, delete=False)
def change_steps(self, context)
Return True if steps changed, False otherwise.
def change_steps(self, context)
Return True if steps changed, False otherwise.
def __init__(self, original_name, new_name, observer, keyword_info=None)
def _check_gherkin(self, new_name, original_name)
def __init__(self, new_basename, get_should_modify_imports)
def __init__(self, reformat=False)
def __init__(self, values, comment)
def change_steps(self, context)
Return True if steps changed, False otherwise.
def __init__(self, new_name, new_value, new_comment)
def change_steps(self, context)
Return True if steps changed, False otherwise.
def __init__(self, row)
Command that will operate on a given logical row of test/user keyword.
def change_steps(self, context)
Return True if steps changed, False otherwise.
Sent after test suite or resource file is renamed.
Definition: messages.py:307
Sent when user selects Save All from File menu or via shortcut.
Definition: messages.py:264
Sent after the file has been actually saved to disk.
Definition: messages.py:259
Sent when user selects Save from File menu or via shortcut.
Definition: messages.py:249
Sent when a resource should be selected.
Definition: messages.py:297
def ExtractScalar(name, value, comment, cell)
def InsertArea(top_left, content)
def ClearArea(top_left, bottom_right)
def PasteArea(top_left, content)
def InsertCells(top_left, bottom_right)
def ExtractList(name, value, comment, cells)
def DeleteCells(top_left, bottom_right)