Robot Framework Integrated Development Environment (RIDE)
tidy.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 # Copyright 2008-2015 Nokia Networks
4 # Copyright 2016- Robot Framework Foundation
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 
18 
31 
32 import os
33 import sys
34 
35 # Allows running as a script. __name__ check needed with multiprocessing:
36 # https://github.com/robotframework/robotframework/issues/1137
37 if 'robot' not in sys.modules and __name__ == '__main__':
38  import pythonpathsetter
39 
40 from robotide.lib.robot.errors import DataError
41 from robotide.lib.robot.parsing import (ResourceFile, TestDataDirectory, TestCaseFile,
42  disable_curdir_processing)
43 from robotide.lib.robot.utils import Application, binary_file_writer, file_writer, PY2
44 
45 
46 USAGE = """robot.tidy -- Robot Framework test data clean-up tool
47 
48 Version: <VERSION>
49 
50 Usage: python -m robot.tidy [options] inputfile
51  or: python -m robot.tidy [options] inputfile [outputfile]
52  or: python -m robot.tidy --inplace [options] inputfile [more input files]
53  or: python -m robot.tidy --recursive [options] directory
54 
55 Tidy tool can be used to clean up and change format of Robot Framework test
56 data files. The output is written into the standard output stream by default,
57 but an optional output file can be given as well. Files can also be modified
58 in-place using --inplace or --recursive options.
59 
60 Options
61 =======
62 
63  -i --inplace Tidy given file(s) so that original file(s) are overwritten
64  (or removed, if the format is changed). When this option is
65  used, it is possible to give multiple input files.
66  Examples:
67  python -m robot.tidy --inplace tests.robot
68  python -m robot.tidy --inplace --format robot *.html
69  -r --recursive Process given directory recursively. Files in the directory
70  are processed in-place similarly as when --inplace option
71  is used.
72  -f --format txt|html|tsv|robot
73  Output file format. If omitted, the format of the input
74  file is used.
75  -p --usepipes Use pipe ('|') as a cell separator in the plain text format.
76  -s --spacecount number
77  The number of spaces between cells in the plain text format.
78  Default is 4.
79  -l --lineseparator native|windows|unix
80  Line separator to use in outputs. The default is 'native'.
81  native: use operating system's native line separators
82  windows: use Windows line separators (CRLF)
83  unix: use Unix line separators (LF)
84  -h -? --help Show this help.
85 
86 Cleaning up the test data
87 =========================
88 
89 Test case files can be normalized using Tidy. Tidy always writes consistent
90 headers, consistent order for settings, and consistent amount of whitespace
91 between sections and cells.
92 
93 Examples:
94  python -m robot.tidy messed_up_tests.robot cleaned_up_tests.robot
95  python -m robot.tidy --inplace tests.robot
96  python -m robot.tidy --recursive path/to/tests
97 
98 Changing the test data format
99 =============================
100 
101 Robot Framework supports test data in various formats, but nowadays the
102 plain text format with the '.robot' extension is the most commonly used.
103 Tidy makes it easy to convert data from one format to another. This is
104 especially useful if there is a need to convert tests in deprecated HTML
105 format to other formats.
106 
107 Input format is always determined based on the extension of the input file.
108 If output file is given, the output format is got from its extension, and
109 when using --inplace or --recursive, it is possible to specify the desired
110 format using the --format option.
111 
112 Examples:
113  python -m robot.tidy tests.html tests.robot
114  python -m robot.tidy --format robot --inplace tests.html
115  python -m robot.tidy --format robot --recursive path/to/tests
116 
117 Output encoding
118 ===============
119 
120 All output files are written using UTF-8 encoding. Outputs written to the
121 console use the current console encoding.
122 
123 Alternative execution
124 =====================
125 
126 In the above examples Tidy is used only with Python, but it works also with
127 Jython and IronPython. Above it is executed as an installed module, but it
128 can also be run as a script like `python path/robot/tidy.py`.
129 
130 For more information about Tidy and other built-in tools, see
131 http://robotframework.org/robotframework/#built-in-tools.
132 """
133 
134 
135 
140 class Tidy():
141 
142  def __init__(self, format='txt', use_pipes=False,
143  space_count=4, line_separator=os.linesep):
144  self._options_options = dict(format=format,
145  pipe_separated=use_pipes,
146  txt_separating_spaces=space_count,
147  line_separator=line_separator)
148 
149 
157  def file(self, path, output=None):
158  data = self._parse_data_parse_data(path)
159  with self._get_writer_get_writer(path, output) as writer:
160  self._save_file_save_file(data, writer)
161  if not output:
162  return writer.getvalue().replace('\r\n', '\n')
163 
164  def _get_writer(self, inpath, outpath):
165  if PY2 and self._is_tsv_is_tsv(inpath):
166  return binary_file_writer(outpath)
167  return file_writer(outpath, newline=self._options_options['line_separator'])
168 
169  def _is_tsv(self, path):
170  format = self._options_options['format'] or os.path.splitext(path)[1][1:]
171  return format.upper() == 'TSV'
172 
173 
177  def inplace(self, *paths):
178  for path in paths:
179  self._save_file_save_file(self._parse_data_parse_data(path))
180 
181 
187  def directory(self, path):
188  self._save_directory_save_directory(self._parse_data_parse_data(path))
189 
190  @disable_curdir_processing
191  def _parse_data(self, path):
192  if os.path.isdir(path):
193  return TestDataDirectory(source=path).populate()
194  if self._is_init_file_is_init_file(path):
195  path = os.path.dirname(path)
196  return TestDataDirectory(source=path).populate(recurse=False)
197  try:
198  return TestCaseFile(source=path).populate()
199  except DataError:
200  try:
201  return ResourceFile(source=path).populate()
202  except DataError:
203  raise DataError("Invalid data source '%s'." % path)
204 
205  def _is_init_file(self, path):
206  return os.path.splitext(os.path.basename(path))[0].lower() == '__init__'
207 
208  def _save_file(self, data, output=None):
209  source = data.initfile if self._is_directory_is_directory(data) else data.source
210  if source and not output:
211  os.remove(source)
212  data.save(output=output, **self._options_options)
213 
214  def _save_directory(self, data):
215  if not self._is_directory_is_directory(data):
216  self._save_file_save_file(data)
217  return
218  if data.initfile:
219  self._save_file_save_file(data)
220  for child in data.children:
221  self._save_directory_save_directory(child)
222 
223  def _is_directory(self, data):
224  return hasattr(data, 'initfile')
225 
226 
227 
232 class TidyCommandLine(Application):
233 
234  def __init__(self):
235  Application.__init__(self, USAGE, arg_limits=(1,))
236 
237  def main(self, arguments, recursive=False, inplace=False, format='txt',
238  usepipes=False, spacecount=4, lineseparator=os.linesep):
239  tidy = Tidy(format=format, use_pipes=usepipes,
240  space_count=spacecount, line_separator=lineseparator)
241  if recursive:
242  tidy.directory(arguments[0])
243  elif inplace:
244  tidy.inplace(*arguments)
245  else:
246  output = tidy.file(*arguments)
247  self.console(output)
248 
249  def validate(self, opts, args):
250  validator = ArgumentValidator()
251  opts['recursive'], opts['inplace'] \
252  = validator.mode_and_arguments(args, **opts)
253  opts['format'] = validator.format(args, **opts)
254  opts['lineseparator'] = validator.line_sep(**opts)
255  if not opts['spacecount']:
256  opts.pop('spacecount')
257  else:
258  opts['spacecount'] = validator.spacecount(opts['spacecount'])
259  return opts, args
260 
261 
263 
264  def mode_and_arguments(self, args, recursive, inplace, **others):
265  recursive, inplace = bool(recursive), bool(inplace)
266  validators = {(True, True): self._recursive_and_inplace_together_recursive_and_inplace_together,
267  (True, False): self._recursive_mode_arguments_recursive_mode_arguments,
268  (False, True): self._inplace_mode_arguments_inplace_mode_arguments,
269  (False, False): self._default_mode_arguments_default_mode_arguments}
270  validator = validators[(recursive, inplace)]
271  validator(args)
272  return recursive, inplace
273 
275  raise DataError('--recursive and --inplace can not be used together.')
276 
277  def _recursive_mode_arguments(self, args):
278  if len(args) != 1:
279  raise DataError('--recursive requires exactly one argument.')
280  if not os.path.isdir(args[0]):
281  raise DataError('--recursive requires input to be a directory.')
282 
283  def _inplace_mode_arguments(self, args):
284  if not all(os.path.isfile(path) for path in args):
285  raise DataError('--inplace requires inputs to be files.')
286 
287  def _default_mode_arguments(self, args):
288  if len(args) not in (1, 2):
289  raise DataError('Default mode requires 1 or 2 arguments.')
290  if not os.path.isfile(args[0]):
291  raise DataError('Default mode requires input to be a file.')
292 
293  def format(self, args, format, inplace, recursive, **others):
294  if not format:
295  if inplace or recursive or len(args) < 2:
296  return None
297  format = os.path.splitext(args[1])[1][1:]
298  format = format.upper()
299  if format not in ('TXT', 'TSV', 'HTML', 'ROBOT'):
300  raise DataError("Invalid format '%s'." % format)
301  return format
302 
303  def line_sep(self, lineseparator, **others):
304  values = {'native': os.linesep, 'windows': '\r\n', 'unix': '\n'}
305  try:
306  return values[(lineseparator or 'native').lower()]
307  except KeyError:
308  raise DataError("Invalid line separator '%s'." % lineseparator)
309 
310  def spacecount(self, spacecount):
311  try:
312  spacecount = int(spacecount)
313  if spacecount < 2:
314  raise ValueError
315  except ValueError:
316  raise DataError('--spacecount must be an integer greater than 1.')
317  return spacecount
318 
319 
320 
330 def tidy_cli(arguments):
331  TidyCommandLine().execute_cli(arguments)
332 
333 
334 if __name__ == '__main__':
335  tidy_cli(sys.argv[1:])
Used when variable does not exist.
Definition: errors.py:67
The parsed resource file object.
Definition: model.py:254
The parsed test case file object.
Definition: model.py:216
The parsed test data directory object.
Definition: model.py:296
def _recursive_mode_arguments(self, args)
Definition: tidy.py:277
def spacecount(self, spacecount)
Definition: tidy.py:310
def _default_mode_arguments(self, args)
Definition: tidy.py:287
def mode_and_arguments(self, args, recursive, inplace, **others)
Definition: tidy.py:264
def _recursive_and_inplace_together(self, args)
Definition: tidy.py:274
def line_sep(self, lineseparator, **others)
Definition: tidy.py:303
def format(self, args, format, inplace, recursive, **others)
Definition: tidy.py:293
def _inplace_mode_arguments(self, args)
Definition: tidy.py:283
Command line interface for the Tidy tool.
Definition: tidy.py:232
def main(self, arguments, recursive=False, inplace=False, format='txt', usepipes=False, spacecount=4, lineseparator=os.linesep)
Definition: tidy.py:238
def validate(self, opts, args)
Definition: tidy.py:249
Programmatic API for the Tidy tool.
Definition: tidy.py:140
def directory(self, path)
Tidy a directory.
Definition: tidy.py:187
def _save_directory(self, data)
Definition: tidy.py:214
def inplace(self, *paths)
Tidy file(s) in-place.
Definition: tidy.py:177
def _is_init_file(self, path)
Definition: tidy.py:205
def __init__(self, format='txt', use_pipes=False, space_count=4, line_separator=os.linesep)
Definition: tidy.py:143
def _get_writer(self, inpath, outpath)
Definition: tidy.py:164
def _parse_data(self, path)
Definition: tidy.py:191
def _save_file(self, data, output=None)
Definition: tidy.py:208
def _is_directory(self, data)
Definition: tidy.py:223
def _is_tsv(self, path)
Definition: tidy.py:169
def file(self, path, output=None)
Tidy a file.
Definition: tidy.py:157
def tidy_cli(arguments)
Executes Tidy similarly as from the command line.
Definition: tidy.py:330
def file_writer(path=None, encoding='UTF-8', newline=None)
Definition: robotio.py:21
def binary_file_writer(path=None)
Definition: robotio.py:37