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
16import fnmatch
17import glob
18import io
19import os
20import shutil
21import sys
22import tempfile
23import time
24
25from robot.version import get_version
26from robot.api import logger
27from robot.utils import (abspath, ConnectionCache, console_decode, del_env_var,
28                         get_env_var, get_env_vars, get_time, is_truthy,
29                         is_unicode, normpath, parse_time, plural_or_not,
30                         secs_to_timestamp, secs_to_timestr, seq2str,
31                         set_env_var, timestr_to_secs, unic, CONSOLE_ENCODING,
32                         IRONPYTHON, JYTHON, PY2, PY3, SYSTEM_ENCODING, WINDOWS)
33
34__version__ = get_version()
35PROCESSES = ConnectionCache('No active processes.')
36
37
38class OperatingSystem(object):
39    """A test library providing keywords for OS related tasks.
40
41    ``OperatingSystem`` is Robot Framework's standard library that
42    enables various operating system related tasks to be performed in
43    the system where Robot Framework is running. It can, among other
44    things, execute commands (e.g. `Run`), create and remove files and
45    directories (e.g. `Create File`, `Remove Directory`), check
46    whether files or directories exists or contain something
47    (e.g. `File Should Exist`, `Directory Should Be Empty`) and
48    manipulate environment variables (e.g. `Set Environment Variable`).
49
50    == Table of contents ==
51
52    - `Path separators`
53    - `Pattern matching`
54    - `Tilde expansion`
55    - `Boolean arguments`
56    - `Example`
57    - `Shortcuts`
58    - `Keywords`
59
60    = Path separators =
61
62    Because Robot Framework uses the backslash (``\\``) as an escape character
63    in the test data, using a literal backslash requires duplicating it like
64    in ``c:\\\\path\\\\file.txt``. That can be inconvenient especially with
65    longer Windows paths, and thus all keywords expecting paths as arguments
66    convert forward slashes to backslashes automatically on Windows. This also
67    means that paths like ``${CURDIR}/path/file.txt`` are operating system
68    independent.
69
70    Notice that the automatic path separator conversion does not work if
71    the path is only a part of an argument like with `Run` and `Start Process`
72    keywords. In these cases the built-in variable ``${/}`` that contains
73    ``\\`` or ``/``, depending on the operating system, can be used instead.
74
75    = Pattern matching =
76
77    Some keywords allow their arguments to be specified as
78    [http://en.wikipedia.org/wiki/Glob_(programming)|glob patterns] where:
79
80    | ``*``        | matches any string, even an empty string                |
81    | ``?``        | matches any single character                            |
82    | ``[chars]``  | matches one character in the bracket                    |
83    | ``[!chars]`` | matches one character not in the bracket                |
84    | ``[a-z]``    | matches one character from the range in the bracket     |
85    | ``[!a-z]``   | matches one character not from the range in the bracket |
86
87    Unless otherwise noted, matching is case-insensitive on
88    case-insensitive operating systems such as Windows.
89
90    Starting from Robot Framework 2.9.1, globbing is not done if the given path
91    matches an existing file even if it would contain a glob pattern.
92
93    = Tilde expansion =
94
95    Paths beginning with ``~`` or ``~username`` are expanded to the current or
96    specified user's home directory, respectively. The resulting path is
97    operating system dependent, but typically e.g. ``~/robot`` is expanded to
98    ``C:\\Users\\<user>\\robot`` on Windows and ``/home/<user>/robot`` on
99    Unixes.
100
101    The ``~username`` form does not work on Jython.
102
103    = Boolean arguments =
104
105    Some keywords accept arguments that are handled as Boolean values true or
106    false. If such an argument is given as a string, it is considered false if
107    it is an empty string or equal to ``FALSE``, ``NONE``, ``NO``, ``OFF`` or
108    ``0``, case-insensitively. Other strings are considered true regardless
109    their value, and other argument types are tested using the same
110    [http://docs.python.org/library/stdtypes.html#truth|rules as in Python].
111
112    True examples:
113    | `Remove Directory` | ${path} | recursive=True    | # Strings are generally true.    |
114    | `Remove Directory` | ${path} | recursive=yes     | # Same as the above.             |
115    | `Remove Directory` | ${path} | recursive=${TRUE} | # Python ``True`` is true.       |
116    | `Remove Directory` | ${path} | recursive=${42}   | # Numbers other than 0 are true. |
117
118    False examples:
119    | `Remove Directory` | ${path} | recursive=False    | # String ``false`` is false.   |
120    | `Remove Directory` | ${path} | recursive=no       | # Also string ``no`` is false. |
121    | `Remove Directory` | ${path} | recursive=${EMPTY} | # Empty string is false.       |
122    | `Remove Directory` | ${path} | recursive=${FALSE} | # Python ``False`` is false.   |
123
124    Considering string ``NONE`` false is new in Robot Framework 3.0.3 and
125    considering also ``OFF`` and ``0`` false is new in Robot Framework 3.1.
126
127    = Example =
128
129    |  =Setting=  |     =Value=     |
130    | Library     | OperatingSystem |
131
132    | =Variable=  |       =Value=         |
133    | ${PATH}     | ${CURDIR}/example.txt |
134
135    | =Test Case= |     =Action=      | =Argument= |    =Argument=        |
136    | Example     | Create File       | ${PATH}    | Some text            |
137    |             | File Should Exist | ${PATH}    |                      |
138    |             | Copy File         | ${PATH}    | ~/file.txt           |
139    |             | ${output} =       | Run | ${TEMPDIR}${/}script.py arg |
140    """
141    ROBOT_LIBRARY_SCOPE = 'GLOBAL'
142    ROBOT_LIBRARY_VERSION = __version__
143
144    def run(self, command):
145        """Runs the given command in the system and returns the output.
146
147        The execution status of the command *is not checked* by this
148        keyword, and it must be done separately based on the returned
149        output. If the execution return code is needed, either `Run
150        And Return RC` or `Run And Return RC And Output` can be used.
151
152        The standard error stream is automatically redirected to the standard
153        output stream by adding ``2>&1`` after the executed command. This
154        automatic redirection is done only when the executed command does not
155        contain additional output redirections. You can thus freely forward
156        the standard error somewhere else, for example, like
157        ``my_command 2>stderr.txt``.
158
159        The returned output contains everything written into the standard
160        output or error streams by the command (unless either of them
161        is redirected explicitly). Many commands add an extra newline
162        (``\\n``) after the output to make it easier to read in the
163        console. To ease processing the returned output, this possible
164        trailing newline is stripped by this keyword.
165
166        Examples:
167        | ${output} =        | Run       | ls -lhF /tmp |
168        | Log                | ${output} |
169        | ${result} =        | Run       | ${CURDIR}${/}tester.py arg1 arg2 |
170        | Should Not Contain | ${result} | FAIL |
171        | ${stdout} =        | Run       | /opt/script.sh 2>/tmp/stderr.txt |
172        | Should Be Equal    | ${stdout} | TEST PASSED |
173        | File Should Be Empty | /tmp/stderr.txt |
174
175        *TIP:* `Run Process` keyword provided by the
176        [http://robotframework.org/robotframework/latest/libraries/Process.html|
177        Process library] supports better process configuration and is generally
178        recommended as a replacement for this keyword.
179        """
180        return self._run(command)[1]
181
182    def run_and_return_rc(self, command):
183        """Runs the given command in the system and returns the return code.
184
185        The return code (RC) is returned as a positive integer in
186        range from 0 to 255 as returned by the executed command. On
187        some operating systems (notable Windows) original return codes
188        can be something else, but this keyword always maps them to
189        the 0-255 range. Since the RC is an integer, it must be
190        checked e.g. with the keyword `Should Be Equal As Integers`
191        instead of `Should Be Equal` (both are built-in keywords).
192
193        Examples:
194        | ${rc} = | Run and Return RC | ${CURDIR}${/}script.py arg |
195        | Should Be Equal As Integers | ${rc} | 0 |
196        | ${rc} = | Run and Return RC | /path/to/example.rb arg1 arg2 |
197        | Should Be True | 0 < ${rc} < 42 |
198
199        See `Run` and `Run And Return RC And Output` if you need to get the
200        output of the executed command.
201
202        *TIP:* `Run Process` keyword provided by the
203        [http://robotframework.org/robotframework/latest/libraries/Process.html|
204        Process library] supports better process configuration and is generally
205        recommended as a replacement for this keyword.
206        """
207        return self._run(command)[0]
208
209    def run_and_return_rc_and_output(self, command):
210        """Runs the given command in the system and returns the RC and output.
211
212        The return code (RC) is returned similarly as with `Run And Return RC`
213        and the output similarly as with `Run`.
214
215        Examples:
216        | ${rc} | ${output} =  | Run and Return RC and Output | ${CURDIR}${/}mytool |
217        | Should Be Equal As Integers | ${rc}    | 0    |
218        | Should Not Contain   | ${output}       | FAIL |
219        | ${rc} | ${stdout} =  | Run and Return RC and Output | /opt/script.sh 2>/tmp/stderr.txt |
220        | Should Be True       | ${rc} > 42      |
221        | Should Be Equal      | ${stdout}       | TEST PASSED |
222        | File Should Be Empty | /tmp/stderr.txt |
223
224        *TIP:* `Run Process` keyword provided by the
225        [http://robotframework.org/robotframework/latest/libraries/Process.html|
226        Process library] supports better process configuration and is generally
227        recommended as a replacement for this keyword.
228        """
229        return self._run(command)
230
231    def _run(self, command):
232        process = _Process(command)
233        self._info("Running command '%s'." % process)
234        stdout = process.read()
235        rc = process.close()
236        return rc, stdout
237
238    def get_file(self, path, encoding='UTF-8', encoding_errors='strict'):
239        """Returns the contents of a specified file.
240
241        This keyword reads the specified file and returns the contents.
242        Line breaks in content are converted to platform independent form.
243        See also `Get Binary File`.
244
245        ``encoding`` defines the encoding of the file. The default value is
246        ``UTF-8``, which means that UTF-8 and ASCII encoded files are read
247        correctly. In addition to the encodings supported by the underlying
248        Python implementation, the following special encoding values can be
249        used:
250
251        - ``SYSTEM``: Use the default system encoding.
252        - ``CONSOLE``: Use the console encoding. Outside Windows this is same
253          as the system encoding.
254
255        ``encoding_errors`` argument controls what to do if decoding some bytes
256        fails. All values accepted by ``decode`` method in Python are valid, but
257        in practice the following values are most useful:
258
259        - ``strict``: Fail if characters cannot be decoded (default).
260        - ``ignore``: Ignore characters that cannot be decoded.
261        - ``replace``: Replace characters that cannot be decoded with
262          a replacement character.
263
264        Support for ``SYSTEM`` and ``CONSOLE`` encodings in Robot Framework 3.0.
265        """
266        path = self._absnorm(path)
267        self._link("Getting file '%s'.", path)
268        encoding = self._map_encoding(encoding)
269        if IRONPYTHON:
270            # https://github.com/IronLanguages/main/issues/1233
271            with open(path) as f:
272                content = f.read().decode(encoding, encoding_errors)
273        else:
274            with io.open(path, encoding=encoding, errors=encoding_errors,
275                         newline='') as f:
276                content = f.read()
277        return content.replace('\r\n', '\n')
278
279    def _map_encoding(self, encoding):
280        # Python 3 opens files in native system encoding by default.
281        if PY3 and encoding.upper() == 'SYSTEM':
282            return None
283        return {'SYSTEM': SYSTEM_ENCODING,
284                'CONSOLE': CONSOLE_ENCODING}.get(encoding.upper(), encoding)
285
286    def get_binary_file(self, path):
287        """Returns the contents of a specified file.
288
289        This keyword reads the specified file and returns the contents as is.
290        See also `Get File`.
291        """
292        path = self._absnorm(path)
293        self._link("Getting file '%s'.", path)
294        with open(path, 'rb') as f:
295            return bytes(f.read())
296
297    def grep_file(self, path, pattern, encoding='UTF-8', encoding_errors='strict'):
298        """Returns the lines of the specified file that match the ``pattern``.
299
300        This keyword reads a file from the file system using the defined
301        ``path``, ``encoding`` and ``encoding_errors`` similarly as `Get File`.
302        A difference is that only the lines that match the given ``pattern`` are
303        returned. Lines are returned as a single string catenated back together
304        with newlines and the number of matched lines is automatically logged.
305        Possible trailing newline is never returned.
306
307        A line matches if it contains the ``pattern`` anywhere in it and
308        it *does not need to match the pattern fully*. The pattern
309        matching syntax is explained in `introduction`, and in this
310        case matching is case-sensitive.
311
312        Examples:
313        | ${errors} = | Grep File | /var/log/myapp.log | ERROR |
314        | ${ret} = | Grep File | ${CURDIR}/file.txt | [Ww]ildc??d ex*ple |
315
316        If more complex pattern matching is needed, it is possible to use
317        `Get File` in combination with String library keywords like `Get
318        Lines Matching Regexp`.
319        """
320        pattern = '*%s*' % pattern
321        path = self._absnorm(path)
322        lines = []
323        total_lines = 0
324        self._link("Reading file '%s'.", path)
325        with io.open(path, encoding=encoding, errors=encoding_errors) as f:
326            for line in f.readlines():
327                total_lines += 1
328                line = line.rstrip('\r\n')
329                if fnmatch.fnmatchcase(line, pattern):
330                    lines.append(line)
331            self._info('%d out of %d lines matched' % (len(lines), total_lines))
332            return '\n'.join(lines)
333
334    def log_file(self, path, encoding='UTF-8', encoding_errors='strict'):
335        """Wrapper for `Get File` that also logs the returned file.
336
337        The file is logged with the INFO level. If you want something else,
338        just use `Get File` and the built-in keyword `Log` with the desired
339        level.
340
341        See `Get File` for more information about ``encoding`` and
342        ``encoding_errors`` arguments.
343        """
344        content = self.get_file(path, encoding, encoding_errors)
345        self._info(content)
346        return content
347
348    # File and directory existence
349
350    def should_exist(self, path, msg=None):
351        """Fails unless the given path (file or directory) exists.
352
353        The path can be given as an exact path or as a glob pattern.
354        The pattern matching syntax is explained in `introduction`.
355        The default error message can be overridden with the ``msg`` argument.
356        """
357        path = self._absnorm(path)
358        if not self._glob(path):
359            self._fail(msg, "Path '%s' does not exist." % path)
360        self._link("Path '%s' exists.", path)
361
362    def should_not_exist(self, path, msg=None):
363        """Fails if the given path (file or directory) exists.
364
365        The path can be given as an exact path or as a glob pattern.
366        The pattern matching syntax is explained in `introduction`.
367        The default error message can be overridden with the ``msg`` argument.
368        """
369        path = self._absnorm(path)
370        matches = self._glob(path)
371        if matches:
372            self._fail(msg, self._get_matches_error('Path', path, matches))
373        self._link("Path '%s' does not exist.", path)
374
375    def _glob(self, path):
376        return glob.glob(path) if not os.path.exists(path) else [path]
377
378    def _get_matches_error(self, what, path, matches):
379        if not self._is_glob_path(path):
380            return "%s '%s' exists." % (what, path)
381        return "%s '%s' matches %s." % (what, path, seq2str(sorted(matches)))
382
383    def _is_glob_path(self, path):
384        return '*' in path or '?' in path or ('[' in path and ']' in path)
385
386    def file_should_exist(self, path, msg=None):
387        """Fails unless the given ``path`` points to an existing file.
388
389        The path can be given as an exact path or as a glob pattern.
390        The pattern matching syntax is explained in `introduction`.
391        The default error message can be overridden with the ``msg`` argument.
392        """
393        path = self._absnorm(path)
394        matches = [p for p in self._glob(path) if os.path.isfile(p)]
395        if not matches:
396            self._fail(msg, "File '%s' does not exist." % path)
397        self._link("File '%s' exists.", path)
398
399    def file_should_not_exist(self, path, msg=None):
400        """Fails if the given path points to an existing file.
401
402        The path can be given as an exact path or as a glob pattern.
403        The pattern matching syntax is explained in `introduction`.
404        The default error message can be overridden with the ``msg`` argument.
405        """
406        path = self._absnorm(path)
407        matches = [p for p in self._glob(path) if os.path.isfile(p)]
408        if matches:
409            self._fail(msg, self._get_matches_error('File', path, matches))
410        self._link("File '%s' does not exist.", path)
411
412    def directory_should_exist(self, path, msg=None):
413        """Fails unless the given path points to an existing directory.
414
415        The path can be given as an exact path or as a glob pattern.
416        The pattern matching syntax is explained in `introduction`.
417        The default error message can be overridden with the ``msg`` argument.
418        """
419        path = self._absnorm(path)
420        matches = [p for p in self._glob(path) if os.path.isdir(p)]
421        if not matches:
422            self._fail(msg, "Directory '%s' does not exist." % path)
423        self._link("Directory '%s' exists.", path)
424
425    def directory_should_not_exist(self, path, msg=None):
426        """Fails if the given path points to an existing file.
427
428        The path can be given as an exact path or as a glob pattern.
429        The pattern matching syntax is explained in `introduction`.
430        The default error message can be overridden with the ``msg`` argument.
431        """
432        path = self._absnorm(path)
433        matches = [p for p in self._glob(path) if os.path.isdir(p)]
434        if matches:
435            self._fail(msg, self._get_matches_error('Directory', path, matches))
436        self._link("Directory '%s' does not exist.", path)
437
438    # Waiting file/dir to appear/disappear
439
440    def wait_until_removed(self, path, timeout='1 minute'):
441        """Waits until the given file or directory is removed.
442
443        The path can be given as an exact path or as a glob pattern.
444        The pattern matching syntax is explained in `introduction`.
445        If the path is a pattern, the keyword waits until all matching
446        items are removed.
447
448        The optional ``timeout`` can be used to control the maximum time of
449        waiting. The timeout is given as a timeout string, e.g. in a format
450        ``15 seconds``, ``1min 10s`` or just ``10``. The time string format is
451        described in an appendix of Robot Framework User Guide.
452
453        If the timeout is negative, the keyword is never timed-out. The keyword
454        returns immediately, if the path does not exist in the first place.
455        """
456        path = self._absnorm(path)
457        timeout = timestr_to_secs(timeout)
458        maxtime = time.time() + timeout
459        while self._glob(path):
460            if timeout >= 0 and time.time() > maxtime:
461                self._fail("'%s' was not removed in %s."
462                           % (path, secs_to_timestr(timeout)))
463            time.sleep(0.1)
464        self._link("'%s' was removed.", path)
465
466    def wait_until_created(self, path, timeout='1 minute'):
467        """Waits until the given file or directory is created.
468
469        The path can be given as an exact path or as a glob pattern.
470        The pattern matching syntax is explained in `introduction`.
471        If the path is a pattern, the keyword returns when an item matching
472        it is created.
473
474        The optional ``timeout`` can be used to control the maximum time of
475        waiting. The timeout is given as a timeout string, e.g. in a format
476        ``15 seconds``, ``1min 10s`` or just ``10``. The time string format is
477        described in an appendix of Robot Framework User Guide.
478
479        If the timeout is negative, the keyword is never timed-out. The keyword
480        returns immediately, if the path already exists.
481        """
482        path = self._absnorm(path)
483        timeout = timestr_to_secs(timeout)
484        maxtime = time.time() + timeout
485        while not self._glob(path):
486            if timeout >= 0 and time.time() > maxtime:
487                self._fail("'%s' was not created in %s."
488                           % (path, secs_to_timestr(timeout)))
489            time.sleep(0.1)
490        self._link("'%s' was created.", path)
491
492    # Dir/file empty
493
494    def directory_should_be_empty(self, path, msg=None):
495        """Fails unless the specified directory is empty.
496
497        The default error message can be overridden with the ``msg`` argument.
498        """
499        path = self._absnorm(path)
500        items = self._list_dir(path)
501        if items:
502            self._fail(msg, "Directory '%s' is not empty. Contents: %s."
503                            % (path, seq2str(items, lastsep=', ')))
504        self._link("Directory '%s' is empty.", path)
505
506    def directory_should_not_be_empty(self, path, msg=None):
507        """Fails if the specified directory is empty.
508
509        The default error message can be overridden with the ``msg`` argument.
510        """
511        path = self._absnorm(path)
512        items = self._list_dir(path)
513        if not items:
514            self._fail(msg, "Directory '%s' is empty." % path)
515        self._link("Directory '%%s' contains %d item%s."
516                   % (len(items), plural_or_not(items)), path)
517
518    def file_should_be_empty(self, path, msg=None):
519        """Fails unless the specified file is empty.
520
521        The default error message can be overridden with the ``msg`` argument.
522        """
523        path = self._absnorm(path)
524        if not os.path.isfile(path):
525            self._error("File '%s' does not exist." % path)
526        size = os.stat(path).st_size
527        if size > 0:
528            self._fail(msg,
529                       "File '%s' is not empty. Size: %d bytes." % (path, size))
530        self._link("File '%s' is empty.", path)
531
532    def file_should_not_be_empty(self, path, msg=None):
533        """Fails if the specified directory is empty.
534
535        The default error message can be overridden with the ``msg`` argument.
536        """
537        path = self._absnorm(path)
538        if not os.path.isfile(path):
539            self._error("File '%s' does not exist." % path)
540        size = os.stat(path).st_size
541        if size == 0:
542            self._fail(msg, "File '%s' is empty." % path)
543        self._link("File '%%s' contains %d bytes." % size, path)
544
545    # Creating and removing files and directory
546
547    def create_file(self, path, content='', encoding='UTF-8'):
548        """Creates a file with the given content and encoding.
549
550        If the directory where the file is created does not exist, it is
551        automatically created along with possible missing intermediate
552        directories. Possible existing file is overwritten.
553
554        On Windows newline characters (``\\n``) in content are automatically
555        converted to Windows native newline sequence (``\\r\\n``).
556
557        See `Get File` for more information about possible ``encoding`` values,
558        including special values ``SYSTEM`` and ``CONSOLE``.
559
560        Examples:
561        | Create File | ${dir}/example.txt | Hello, world!       |         |
562        | Create File | ${path}            | Hyv\\xe4 esimerkki  | Latin-1 |
563        | Create File | /tmp/foo.txt       | 3\\nlines\\nhere\\n | SYSTEM  |
564
565        Use `Append To File` if you want to append to an existing file
566        and `Create Binary File` if you need to write bytes without encoding.
567        `File Should Not Exist` can be used to avoid overwriting existing
568        files.
569
570        The support for ``SYSTEM`` and ``CONSOLE`` encodings is new in Robot
571        Framework 3.0. Automatically converting ``\\n`` to ``\\r\\n`` on
572        Windows is new in Robot Framework 3.1.
573        """
574        path = self._write_to_file(path, content, encoding)
575        self._link("Created file '%s'.", path)
576
577    def _write_to_file(self, path, content, encoding=None, mode='w'):
578        path = self._absnorm(path)
579        parent = os.path.dirname(path)
580        if not os.path.exists(parent):
581            os.makedirs(parent)
582        # io.open() only accepts Unicode, not byte-strings, in text mode.
583        # We expect possible byte-strings to be all ASCII.
584        if PY2 and isinstance(content, str) and 'b' not in mode:
585            content = unicode(content)
586        if encoding:
587            encoding = self._map_encoding(encoding)
588        with io.open(path, mode, encoding=encoding) as f:
589            f.write(content)
590        return path
591
592    def create_binary_file(self, path, content):
593        """Creates a binary file with the given content.
594
595        If content is given as a Unicode string, it is first converted to bytes
596        character by character. All characters with ordinal below 256 can be
597        used and are converted to bytes with same values. Using characters
598        with higher ordinal is an error.
599
600        Byte strings, and possible other types, are written to the file as is.
601
602        If the directory for the file does not exist, it is created, along
603        with missing intermediate directories.
604
605        Examples:
606        | Create Binary File | ${dir}/example.png | ${image content}     |
607        | Create Binary File | ${path}            | \\x01\\x00\\xe4\\x00 |
608
609        Use `Create File` if you want to create a text file using a certain
610        encoding. `File Should Not Exist` can be used to avoid overwriting
611        existing files.
612        """
613        if is_unicode(content):
614            content = bytes(bytearray(ord(c) for c in content))
615        path = self._write_to_file(path, content, mode='wb')
616        self._link("Created binary file '%s'.", path)
617
618    def append_to_file(self, path, content, encoding='UTF-8'):
619        """Appends the given content to the specified file.
620
621        If the file exists, the given text is written to its end. If the file
622        does not exist, it is created.
623
624        Other than not overwriting possible existing files, this keyword works
625        exactly like `Create File`. See its documentation for more details
626        about the usage.
627
628        Note that special encodings ``SYSTEM`` and ``CONSOLE`` only work
629        with this keyword starting from Robot Framework 3.1.2.
630        """
631        path = self._write_to_file(path, content, encoding, mode='a')
632        self._link("Appended to file '%s'.", path)
633
634    def remove_file(self, path):
635        """Removes a file with the given path.
636
637        Passes if the file does not exist, but fails if the path does
638        not point to a regular file (e.g. it points to a directory).
639
640        The path can be given as an exact path or as a glob pattern.
641        The pattern matching syntax is explained in `introduction`.
642        If the path is a pattern, all files matching it are removed.
643        """
644        path = self._absnorm(path)
645        matches = self._glob(path)
646        if not matches:
647            self._link("File '%s' does not exist.", path)
648        for match in matches:
649            if not os.path.isfile(match):
650                self._error("Path '%s' is not a file." % match)
651            os.remove(match)
652            self._link("Removed file '%s'.", match)
653
654    def remove_files(self, *paths):
655        """Uses `Remove File` to remove multiple files one-by-one.
656
657        Example:
658        | Remove Files | ${TEMPDIR}${/}foo.txt | ${TEMPDIR}${/}bar.txt | ${TEMPDIR}${/}zap.txt |
659        """
660        for path in paths:
661            self.remove_file(path)
662
663    def empty_directory(self, path):
664        """Deletes all the content from the given directory.
665
666        Deletes both files and sub-directories, but the specified directory
667        itself if not removed. Use `Remove Directory` if you want to remove
668        the whole directory.
669        """
670        path = self._absnorm(path)
671        for item in self._list_dir(path, absolute=True):
672            if os.path.isdir(item):
673                shutil.rmtree(item)
674            else:
675                os.remove(item)
676        self._link("Emptied directory '%s'.", path)
677
678    def create_directory(self, path):
679        """Creates the specified directory.
680
681        Also possible intermediate directories are created. Passes if the
682        directory already exists, but fails if the path exists and is not
683        a directory.
684        """
685        path = self._absnorm(path)
686        if os.path.isdir(path):
687            self._link("Directory '%s' already exists.", path )
688        elif os.path.exists(path):
689            self._error("Path '%s' is not a directory." % path)
690        else:
691            os.makedirs(path)
692            self._link("Created directory '%s'.", path)
693
694    def remove_directory(self, path, recursive=False):
695        """Removes the directory pointed to by the given ``path``.
696
697        If the second argument ``recursive`` is given a true value (see
698        `Boolean arguments`), the directory is removed recursively. Otherwise
699        removing fails if the directory is not empty.
700
701        If the directory pointed to by the ``path`` does not exist, the keyword
702        passes, but it fails, if the ``path`` points to a file.
703        """
704        path = self._absnorm(path)
705        if not os.path.exists(path):
706            self._link("Directory '%s' does not exist.", path)
707        elif not os.path.isdir(path):
708            self._error("Path '%s' is not a directory." % path)
709        else:
710            if is_truthy(recursive):
711                shutil.rmtree(path)
712            else:
713                self.directory_should_be_empty(
714                    path, "Directory '%s' is not empty." % path)
715                os.rmdir(path)
716            self._link("Removed directory '%s'.", path)
717
718    # Moving and copying files and directories
719
720    def copy_file(self, source, destination):
721        """Copies the source file into the destination.
722
723        Source must be a path to an existing file or a glob pattern (see
724        `Pattern matching`) that matches exactly one file. How the
725        destination is interpreted is explained below.
726
727        1) If the destination is an existing file, the source file is copied
728        over it.
729
730        2) If the destination is an existing directory, the source file is
731        copied into it. A possible file with the same name as the source is
732        overwritten.
733
734        3) If the destination does not exist and it ends with a path
735        separator (``/`` or ``\\``), it is considered a directory. That
736        directory is created and a source file copied into it.
737        Possible missing intermediate directories are also created.
738
739        4) If the destination does not exist and it does not end with a path
740        separator, it is considered a file. If the path to the file does not
741        exist, it is created.
742
743        The resulting destination path is returned since Robot Framework 2.9.2.
744
745        See also `Copy Files`, `Move File`, and `Move Files`.
746        """
747        source, destination = \
748            self._prepare_copy_and_move_file(source, destination)
749        if not self._are_source_and_destination_same_file(source, destination):
750            source, destination = self._atomic_copy(source, destination)
751            self._link("Copied file from '%s' to '%s'.", source, destination)
752        return destination
753
754    def _prepare_copy_and_move_file(self, source, destination):
755        source = self._normalize_copy_and_move_source(source)
756        destination = self._normalize_copy_and_move_destination(destination)
757        if os.path.isdir(destination):
758            destination = os.path.join(destination, os.path.basename(source))
759        return source, destination
760
761    def _normalize_copy_and_move_source(self, source):
762        source = self._absnorm(source)
763        sources = self._glob(source)
764        if len(sources) > 1:
765            self._error("Multiple matches with source pattern '%s'." % source)
766        if sources:
767            source = sources[0]
768        if not os.path.exists(source):
769            self._error("Source file '%s' does not exist." % source)
770        if not os.path.isfile(source):
771            self._error("Source file '%s' is not a regular file." % source)
772        return source
773
774    def _normalize_copy_and_move_destination(self, destination):
775        is_dir = os.path.isdir(destination) or destination.endswith(('/', '\\'))
776        destination = self._absnorm(destination)
777        directory = destination if is_dir else os.path.dirname(destination)
778        self._ensure_destination_directory_exists(directory)
779        return destination
780
781    def _ensure_destination_directory_exists(self, path):
782        if not os.path.exists(path):
783            os.makedirs(path)
784        elif not os.path.isdir(path):
785            self._error("Destination '%s' exists and is not a directory." % path)
786
787    def _are_source_and_destination_same_file(self, source, destination):
788        if self._force_normalize(source) == self._force_normalize(destination):
789            self._link("Source '%s' and destination '%s' point to the same "
790                       "file.", source, destination)
791            return True
792        return False
793
794    def _force_normalize(self, path):
795        # TODO: Should normalize_path also support link normalization?
796        # TODO: Should we handle dos paths like 'exampl~1.txt'?
797        return os.path.realpath(normpath(path, case_normalize=True))
798
799    def _atomic_copy(self, source, destination):
800        """Copy file atomically (or at least try to).
801
802        This method tries to ensure that a file copy operation will not fail
803        if the destination file is removed during copy operation. The problem
804        is that copying a file is typically not an atomic operation.
805
806        Luckily moving files is atomic in almost every platform, assuming files
807        are on the same filesystem, and we can use that as a workaround:
808        - First move the source to a temporary directory that is ensured to
809          be on the same filesystem as the destination.
810        - Move the temporary file over the real destination.
811
812        See also https://github.com/robotframework/robotframework/issues/1502
813        """
814        temp_directory = tempfile.mkdtemp(dir=os.path.dirname(destination))
815        temp_file = os.path.join(temp_directory, os.path.basename(source))
816        try:
817            shutil.copy(source, temp_file)
818            if os.path.exists(destination):
819                os.remove(destination)
820            shutil.move(temp_file, destination)
821        finally:
822            shutil.rmtree(temp_directory)
823        return source, destination
824
825    def move_file(self, source, destination):
826        """Moves the source file into the destination.
827
828        Arguments have exactly same semantics as with `Copy File` keyword.
829        Destination file path is returned since Robot Framework 2.9.2.
830
831        If the source and destination are on the same filesystem, rename
832        operation is used. Otherwise file is copied to the destination
833        filesystem and then removed from the original filesystem.
834
835        See also `Move Files`, `Copy File`, and `Copy Files`.
836        """
837        source, destination = \
838            self._prepare_copy_and_move_file(source, destination)
839        if not self._are_source_and_destination_same_file(destination, source):
840            shutil.move(source, destination)
841            self._link("Moved file from '%s' to '%s'.", source, destination)
842        return destination
843
844    def copy_files(self, *sources_and_destination):
845        """Copies specified files to the target directory.
846
847        Source files can be given as exact paths and as glob patterns (see
848        `Pattern matching`). At least one source must be given, but it is
849        not an error if it is a pattern that does not match anything.
850
851        Last argument must be the destination directory. If the destination
852        does not exist, it will be created.
853
854        Examples:
855        | Copy Files | ${dir}/file1.txt  | ${dir}/file2.txt | ${dir2} |
856        | Copy Files | ${dir}/file-*.txt | ${dir2}          |         |
857
858        See also `Copy File`, `Move File`, and `Move Files`.
859        """
860        sources, destination \
861            = self._prepare_copy_and_move_files(sources_and_destination)
862        for source in sources:
863            self.copy_file(source, destination)
864
865    def _prepare_copy_and_move_files(self, items):
866        if len(items) < 2:
867            self._error('Must contain destination and at least one source.')
868        sources = self._glob_files(items[:-1])
869        destination = self._absnorm(items[-1])
870        self._ensure_destination_directory_exists(destination)
871        return sources, destination
872
873    def _glob_files(self, patterns):
874        files = []
875        for pattern in patterns:
876            files.extend(self._glob(self._absnorm(pattern)))
877        return files
878
879    def move_files(self, *sources_and_destination):
880        """Moves specified files to the target directory.
881
882        Arguments have exactly same semantics as with `Copy Files` keyword.
883
884        See also `Move File`, `Copy File`, and `Copy Files`.
885        """
886        sources, destination \
887            = self._prepare_copy_and_move_files(sources_and_destination)
888        for source in sources:
889            self.move_file(source, destination)
890
891    def copy_directory(self, source, destination):
892        """Copies the source directory into the destination.
893
894        If the destination exists, the source is copied under it. Otherwise
895        the destination directory and the possible missing intermediate
896        directories are created.
897        """
898        source, destination \
899            = self._prepare_copy_and_move_directory(source, destination)
900        try:
901            shutil.copytree(source, destination)
902        except shutil.Error:
903            # https://github.com/robotframework/robotframework/issues/2321
904            if not (WINDOWS and JYTHON):
905                raise
906        self._link("Copied directory from '%s' to '%s'.", source, destination)
907
908    def _prepare_copy_and_move_directory(self, source, destination):
909        source = self._absnorm(source)
910        destination = self._absnorm(destination)
911        if not os.path.exists(source):
912            self._error("Source '%s' does not exist." % source)
913        if not os.path.isdir(source):
914            self._error("Source '%s' is not a directory." % source)
915        if os.path.exists(destination) and not os.path.isdir(destination):
916            self._error("Destination '%s' is not a directory." % destination)
917        if os.path.exists(destination):
918            base = os.path.basename(source)
919            destination = os.path.join(destination, base)
920        else:
921            parent = os.path.dirname(destination)
922            if not os.path.exists(parent):
923                os.makedirs(parent)
924        return source, destination
925
926    def move_directory(self, source, destination):
927        """Moves the source directory into a destination.
928
929        Uses `Copy Directory` keyword internally, and ``source`` and
930        ``destination`` arguments have exactly same semantics as with
931        that keyword.
932        """
933        source, destination \
934            = self._prepare_copy_and_move_directory(source, destination)
935        shutil.move(source, destination)
936        self._link("Moved directory from '%s' to '%s'.", source, destination)
937
938    # Environment Variables
939
940    def get_environment_variable(self, name, default=None):
941        """Returns the value of an environment variable with the given name.
942
943        If no such environment variable is set, returns the default value, if
944        given. Otherwise fails the test case.
945
946        Returned variables are automatically decoded to Unicode using
947        the system encoding.
948
949        Note that you can also access environment variables directly using
950        the variable syntax ``%{ENV_VAR_NAME}``.
951        """
952        value = get_env_var(name, default)
953        if value is None:
954            self._error("Environment variable '%s' does not exist." % name)
955        return value
956
957    def set_environment_variable(self, name, value):
958        """Sets an environment variable to a specified value.
959
960        Values are converted to strings automatically. Set variables are
961        automatically encoded using the system encoding.
962        """
963        set_env_var(name, value)
964        self._info("Environment variable '%s' set to value '%s'."
965                   % (name, value))
966
967    def append_to_environment_variable(self, name, *values, **config):
968        """Appends given ``values`` to environment variable ``name``.
969
970        If the environment variable already exists, values are added after it,
971        and otherwise a new environment variable is created.
972
973        Values are, by default, joined together using the operating system
974        path separator (``;`` on Windows, ``:`` elsewhere). This can be changed
975        by giving a separator after the values like ``separator=value``. No
976        other configuration parameters are accepted.
977
978        Examples (assuming ``NAME`` and ``NAME2`` do not exist initially):
979        | Append To Environment Variable | NAME     | first  |       |
980        | Should Be Equal                | %{NAME}  | first  |       |
981        | Append To Environment Variable | NAME     | second | third |
982        | Should Be Equal                | %{NAME}  | first${:}second${:}third |
983        | Append To Environment Variable | NAME2    | first  | separator=-     |
984        | Should Be Equal                | %{NAME2} | first  |                 |
985        | Append To Environment Variable | NAME2    | second | separator=-     |
986        | Should Be Equal                | %{NAME2} | first-second             |
987        """
988        sentinel = object()
989        initial = self.get_environment_variable(name, sentinel)
990        if initial is not sentinel:
991            values = (initial,) + values
992        separator = config.pop('separator', os.pathsep)
993        if config:
994            config = ['='.join(i) for i in sorted(config.items())]
995            self._error('Configuration %s not accepted.'
996                        % seq2str(config, lastsep=' or '))
997        self.set_environment_variable(name, separator.join(values))
998
999    def remove_environment_variable(self, *names):
1000        """Deletes the specified environment variable.
1001
1002        Does nothing if the environment variable is not set.
1003
1004        It is possible to remove multiple variables by passing them to this
1005        keyword as separate arguments.
1006        """
1007        for name in names:
1008            value = del_env_var(name)
1009            if value:
1010                self._info("Environment variable '%s' deleted." % name)
1011            else:
1012                self._info("Environment variable '%s' does not exist." % name)
1013
1014    def environment_variable_should_be_set(self, name, msg=None):
1015        """Fails if the specified environment variable is not set.
1016
1017        The default error message can be overridden with the ``msg`` argument.
1018        """
1019        value = get_env_var(name)
1020        if not value:
1021            self._fail(msg, "Environment variable '%s' is not set." % name)
1022        self._info("Environment variable '%s' is set to '%s'." % (name, value))
1023
1024    def environment_variable_should_not_be_set(self, name, msg=None):
1025        """Fails if the specified environment variable is set.
1026
1027        The default error message can be overridden with the ``msg`` argument.
1028        """
1029        value = get_env_var(name)
1030        if value:
1031            self._fail(msg, "Environment variable '%s' is set to '%s'."
1032                            % (name, value))
1033        self._info("Environment variable '%s' is not set." % name)
1034
1035    def get_environment_variables(self):
1036        """Returns currently available environment variables as a dictionary.
1037
1038        Both keys and values are decoded to Unicode using the system encoding.
1039        Altering the returned dictionary has no effect on the actual environment
1040        variables.
1041        """
1042        return get_env_vars()
1043
1044    def log_environment_variables(self, level='INFO'):
1045        """Logs all environment variables using the given log level.
1046
1047        Environment variables are also returned the same way as with
1048        `Get Environment Variables` keyword.
1049        """
1050        variables = get_env_vars()
1051        for name in sorted(variables, key=lambda item: item.lower()):
1052            self._log('%s = %s' % (name, variables[name]), level)
1053        return variables
1054
1055    # Path
1056
1057    def join_path(self, base, *parts):
1058        """Joins the given path part(s) to the given base path.
1059
1060        The path separator (``/`` or ``\\``) is inserted when needed and
1061        the possible absolute paths handled as expected. The resulted
1062        path is also normalized.
1063
1064        Examples:
1065        | ${path} = | Join Path | my        | path  |
1066        | ${p2} =   | Join Path | my/       | path/ |
1067        | ${p3} =   | Join Path | my        | path  | my | file.txt |
1068        | ${p4} =   | Join Path | my        | /path |
1069        | ${p5} =   | Join Path | /my/path/ | ..    | path2 |
1070        =>
1071        - ${path} = 'my/path'
1072        - ${p2} = 'my/path'
1073        - ${p3} = 'my/path/my/file.txt'
1074        - ${p4} = '/path'
1075        - ${p5} = '/my/path2'
1076        """
1077        base = base.replace('/', os.sep)
1078        parts = [p.replace('/', os.sep) for p in parts]
1079        return self.normalize_path(os.path.join(base, *parts))
1080
1081    def join_paths(self, base, *paths):
1082        """Joins given paths with base and returns resulted paths.
1083
1084        See `Join Path` for more information.
1085
1086        Examples:
1087        | @{p1} = | Join Paths | base     | example       | other |          |
1088        | @{p2} = | Join Paths | /my/base | /example      | other |          |
1089        | @{p3} = | Join Paths | my/base  | example/path/ | other | one/more |
1090        =>
1091        - @{p1} = ['base/example', 'base/other']
1092        - @{p2} = ['/example', '/my/base/other']
1093        - @{p3} = ['my/base/example/path', 'my/base/other', 'my/base/one/more']
1094        """
1095        return [self.join_path(base, path) for path in paths]
1096
1097    def normalize_path(self, path, case_normalize=False):
1098        """Normalizes the given path.
1099
1100        - Collapses redundant separators and up-level references.
1101        - Converts ``/`` to ``\\`` on Windows.
1102        - Replaces initial ``~`` or ``~user`` by that user's home directory.
1103          The latter is not supported on Jython.
1104        - If ``case_normalize`` is given a true value (see `Boolean arguments`)
1105          on Windows, converts the path to all lowercase. New in Robot
1106          Framework 3.1.
1107
1108        Examples:
1109        | ${path1} = | Normalize Path | abc/           |
1110        | ${path2} = | Normalize Path | abc/../def     |
1111        | ${path3} = | Normalize Path | abc/./def//ghi |
1112        | ${path4} = | Normalize Path | ~robot/stuff   |
1113        =>
1114        - ${path1} = 'abc'
1115        - ${path2} = 'def'
1116        - ${path3} = 'abc/def/ghi'
1117        - ${path4} = '/home/robot/stuff'
1118
1119        On Windows result would use ``\\`` instead of ``/`` and home directory
1120        would be different.
1121        """
1122        path = os.path.normpath(os.path.expanduser(path.replace('/', os.sep)))
1123        # os.path.normcase doesn't normalize on OSX which also, by default,
1124        # has case-insensitive file system. Our robot.utils.normpath would
1125        # do that, but it's not certain would that, or other things that the
1126        # utility do, desirable.
1127        if case_normalize:
1128            path = os.path.normcase(path)
1129        return path or '.'
1130
1131    def split_path(self, path):
1132        """Splits the given path from the last path separator (``/`` or ``\\``).
1133
1134        The given path is first normalized (e.g. a possible trailing
1135        path separator is removed, special directories ``..`` and ``.``
1136        removed). The parts that are split are returned as separate
1137        components.
1138
1139        Examples:
1140        | ${path1} | ${dir} =  | Split Path | abc/def         |
1141        | ${path2} | ${file} = | Split Path | abc/def/ghi.txt |
1142        | ${path3} | ${d2}  =  | Split Path | abc/../def/ghi/ |
1143        =>
1144        - ${path1} = 'abc' & ${dir} = 'def'
1145        - ${path2} = 'abc/def' & ${file} = 'ghi.txt'
1146        - ${path3} = 'def' & ${d2} = 'ghi'
1147        """
1148        return os.path.split(self.normalize_path(path))
1149
1150    def split_extension(self, path):
1151        """Splits the extension from the given path.
1152
1153        The given path is first normalized (e.g. possible trailing
1154        path separators removed, special directories ``..`` and ``.``
1155        removed). The base path and extension are returned as separate
1156        components so that the dot used as an extension separator is
1157        removed. If the path contains no extension, an empty string is
1158        returned for it. Possible leading and trailing dots in the file
1159        name are never considered to be extension separators.
1160
1161        Examples:
1162        | ${path} | ${ext} = | Split Extension | file.extension    |
1163        | ${p2}   | ${e2} =  | Split Extension | path/file.ext     |
1164        | ${p3}   | ${e3} =  | Split Extension | path/file         |
1165        | ${p4}   | ${e4} =  | Split Extension | p1/../p2/file.ext |
1166        | ${p5}   | ${e5} =  | Split Extension | path/.file.ext    |
1167        | ${p6}   | ${e6} =  | Split Extension | path/.file        |
1168        =>
1169        - ${path} = 'file' & ${ext} = 'extension'
1170        - ${p2} = 'path/file' & ${e2} = 'ext'
1171        - ${p3} = 'path/file' & ${e3} = ''
1172        - ${p4} = 'p2/file' & ${e4} = 'ext'
1173        - ${p5} = 'path/.file' & ${e5} = 'ext'
1174        - ${p6} = 'path/.file' & ${e6} = ''
1175        """
1176        path = self.normalize_path(path)
1177        basename = os.path.basename(path)
1178        if basename.startswith('.' * basename.count('.')):
1179            return path, ''
1180        if path.endswith('.'):
1181            path2 = path.rstrip('.')
1182            trailing_dots = '.' * (len(path) - len(path2))
1183            path = path2
1184        else:
1185            trailing_dots = ''
1186        basepath, extension = os.path.splitext(path)
1187        if extension.startswith('.'):
1188            extension = extension[1:]
1189        if extension:
1190            extension += trailing_dots
1191        else:
1192            basepath += trailing_dots
1193        return basepath, extension
1194
1195    # Misc
1196
1197    def get_modified_time(self, path, format='timestamp'):
1198        """Returns the last modification time of a file or directory.
1199
1200        How time is returned is determined based on the given ``format``
1201        string as follows. Note that all checks are case-insensitive.
1202        Returned time is also automatically logged.
1203
1204        1) If ``format`` contains the word ``epoch``, the time is returned
1205           in seconds after the UNIX epoch. The return value is always
1206           an integer.
1207
1208        2) If ``format`` contains any of the words ``year``, ``month``,
1209           ``day``, ``hour``, ``min`` or ``sec``, only the selected parts are
1210           returned. The order of the returned parts is always the one
1211           in the previous sentence and the order of the words in
1212           ``format`` is not significant. The parts are returned as
1213           zero-padded strings (e.g. May -> ``05``).
1214
1215        3) Otherwise, and by default, the time is returned as a
1216           timestamp string in the format ``2006-02-24 15:08:31``.
1217
1218        Examples (when the modified time of ``${CURDIR}`` is
1219        2006-03-29 15:06:21):
1220        | ${time} = | Get Modified Time | ${CURDIR} |
1221        | ${secs} = | Get Modified Time | ${CURDIR} | epoch |
1222        | ${year} = | Get Modified Time | ${CURDIR} | return year |
1223        | ${y} | ${d} = | Get Modified Time | ${CURDIR} | year,day |
1224        | @{time} = | Get Modified Time | ${CURDIR} | year,month,day,hour,min,sec |
1225        =>
1226        - ${time} = '2006-03-29 15:06:21'
1227        - ${secs} = 1143637581
1228        - ${year} = '2006'
1229        - ${y} = '2006' & ${d} = '29'
1230        - @{time} = ['2006', '03', '29', '15', '06', '21']
1231        """
1232        path = self._absnorm(path)
1233        if not os.path.exists(path):
1234            self._error("Path '%s' does not exist." % path)
1235        mtime = get_time(format, os.stat(path).st_mtime)
1236        self._link("Last modified time of '%%s' is %s." % mtime, path)
1237        return mtime
1238
1239    def set_modified_time(self, path, mtime):
1240        """Sets the file modification and access times.
1241
1242        Changes the modification and access times of the given file to
1243        the value determined by ``mtime``. The time can be given in
1244        different formats described below. Note that all checks
1245        involving strings are case-insensitive. Modified time can only
1246        be set to regular files.
1247
1248        1) If ``mtime`` is a number, or a string that can be converted
1249           to a number, it is interpreted as seconds since the UNIX
1250           epoch (1970-01-01 00:00:00 UTC). This documentation was
1251           originally written about 1177654467 seconds after the epoch.
1252
1253        2) If ``mtime`` is a timestamp, that time will be used. Valid
1254           timestamp formats are ``YYYY-MM-DD hh:mm:ss`` and
1255           ``YYYYMMDD hhmmss``.
1256
1257        3) If ``mtime`` is equal to ``NOW``, the current local time is used.
1258
1259        4) If ``mtime`` is equal to ``UTC``, the current time in
1260           [http://en.wikipedia.org/wiki/Coordinated_Universal_Time|UTC]
1261           is used.
1262
1263        5) If ``mtime`` is in the format like ``NOW - 1 day`` or ``UTC + 1
1264           hour 30 min``, the current local/UTC time plus/minus the time
1265           specified with the time string is used. The time string format
1266           is described in an appendix of Robot Framework User Guide.
1267
1268        Examples:
1269        | Set Modified Time | /path/file | 1177654467         | # Time given as epoch seconds |
1270        | Set Modified Time | /path/file | 2007-04-27 9:14:27 | # Time given as a timestamp   |
1271        | Set Modified Time | /path/file | NOW                | # The local time of execution |
1272        | Set Modified Time | /path/file | NOW - 1 day        | # 1 day subtracted from the local time |
1273        | Set Modified Time | /path/file | UTC + 1h 2min 3s   | # 1h 2min 3s added to the UTC time |
1274        """
1275        mtime = parse_time(mtime)
1276        path = self._absnorm(path)
1277        if not os.path.exists(path):
1278            self._error("File '%s' does not exist." % path)
1279        if not os.path.isfile(path):
1280            self._error("Path '%s' is not a regular file." % path)
1281        os.utime(path, (mtime, mtime))
1282        time.sleep(0.1)  # Give os some time to really set these times
1283        tstamp = secs_to_timestamp(mtime, seps=('-', ' ', ':'))
1284        self._link("Set modified time of '%%s' to %s." % tstamp, path)
1285
1286    def get_file_size(self, path):
1287        """Returns and logs file size as an integer in bytes."""
1288        path = self._absnorm(path)
1289        if not os.path.isfile(path):
1290            self._error("File '%s' does not exist." % path)
1291        size = os.stat(path).st_size
1292        plural = plural_or_not(size)
1293        self._link("Size of file '%%s' is %d byte%s." % (size, plural), path)
1294        return size
1295
1296    def list_directory(self, path, pattern=None, absolute=False):
1297        """Returns and logs items in a directory, optionally filtered with ``pattern``.
1298
1299        File and directory names are returned in case-sensitive alphabetical
1300        order, e.g. ``['A Name', 'Second', 'a lower case name', 'one more']``.
1301        Implicit directories ``.`` and ``..`` are not returned. The returned
1302        items are automatically logged.
1303
1304        File and directory names are returned relative to the given path
1305        (e.g. ``'file.txt'``) by default. If you want them be returned in
1306        absolute format (e.g. ``'/home/robot/file.txt'``), give the ``absolute``
1307        argument a true value (see `Boolean arguments`).
1308
1309        If ``pattern`` is given, only items matching it are returned. The pattern
1310        matching syntax is explained in `introduction`, and in this case
1311        matching is case-sensitive.
1312
1313        Examples (using also other `List Directory` variants):
1314        | @{items} = | List Directory           | ${TEMPDIR} |
1315        | @{files} = | List Files In Directory  | /tmp | *.txt | absolute |
1316        | ${count} = | Count Files In Directory | ${CURDIR} | ??? |
1317        """
1318        items = self._list_dir(path, pattern, absolute)
1319        self._info('%d item%s:\n%s' % (len(items), plural_or_not(items),
1320                                       '\n'.join(items)))
1321        return items
1322
1323    def list_files_in_directory(self, path, pattern=None, absolute=False):
1324        """Wrapper for `List Directory` that returns only files."""
1325        files = self._list_files_in_dir(path, pattern, absolute)
1326        self._info('%d file%s:\n%s' % (len(files), plural_or_not(files),
1327                                       '\n'.join(files)))
1328        return files
1329
1330    def list_directories_in_directory(self, path, pattern=None, absolute=False):
1331        """Wrapper for `List Directory` that returns only directories."""
1332        dirs = self._list_dirs_in_dir(path, pattern, absolute)
1333        self._info('%d director%s:\n%s' % (len(dirs),
1334                                           'y' if len(dirs) == 1 else 'ies',
1335                                           '\n'.join(dirs)))
1336        return dirs
1337
1338    def count_items_in_directory(self, path, pattern=None):
1339        """Returns and logs the number of all items in the given directory.
1340
1341        The argument ``pattern`` has the same semantics as with `List Directory`
1342        keyword. The count is returned as an integer, so it must be checked e.g.
1343        with the built-in keyword `Should Be Equal As Integers`.
1344        """
1345        count = len(self._list_dir(path, pattern))
1346        self._info("%s item%s." % (count, plural_or_not(count)))
1347        return count
1348
1349    def count_files_in_directory(self, path, pattern=None):
1350        """Wrapper for `Count Items In Directory` returning only file count."""
1351        count = len(self._list_files_in_dir(path, pattern))
1352        self._info("%s file%s." % (count, plural_or_not(count)))
1353        return count
1354
1355    def count_directories_in_directory(self, path, pattern=None):
1356        """Wrapper for `Count Items In Directory` returning only directory count."""
1357        count = len(self._list_dirs_in_dir(path, pattern))
1358        self._info("%s director%s." % (count, 'y' if count == 1 else 'ies'))
1359        return count
1360
1361    def _list_dir(self, path, pattern=None, absolute=False):
1362        path = self._absnorm(path)
1363        self._link("Listing contents of directory '%s'.", path)
1364        if not os.path.isdir(path):
1365            self._error("Directory '%s' does not exist." % path)
1366        # result is already unicode but unic also handles NFC normalization
1367        items = sorted(unic(item) for item in os.listdir(path))
1368        if pattern:
1369            items = [i for i in items if fnmatch.fnmatchcase(i, pattern)]
1370        if is_truthy(absolute):
1371            path = os.path.normpath(path)
1372            items = [os.path.join(path, item) for item in items]
1373        return items
1374
1375    def _list_files_in_dir(self, path, pattern=None, absolute=False):
1376        return [item for item in self._list_dir(path, pattern, absolute)
1377                if os.path.isfile(os.path.join(path, item))]
1378
1379    def _list_dirs_in_dir(self, path, pattern=None, absolute=False):
1380        return [item for item in self._list_dir(path, pattern, absolute)
1381                if os.path.isdir(os.path.join(path, item))]
1382
1383    def touch(self, path):
1384        """Emulates the UNIX touch command.
1385
1386        Creates a file, if it does not exist. Otherwise changes its access and
1387        modification times to the current time.
1388
1389        Fails if used with the directories or the parent directory of the given
1390        file does not exist.
1391        """
1392        path = self._absnorm(path)
1393        if os.path.isdir(path):
1394            self._error("Cannot touch '%s' because it is a directory." % path)
1395        if not os.path.exists(os.path.dirname(path)):
1396            self._error("Cannot touch '%s' because its parent directory does "
1397                        "not exist." % path)
1398        if os.path.exists(path):
1399            mtime = round(time.time())
1400            os.utime(path, (mtime, mtime))
1401            self._link("Touched existing file '%s'.", path)
1402        else:
1403            open(path, 'w').close()
1404            self._link("Touched new file '%s'.", path)
1405
1406    def _absnorm(self, path):
1407        path = self.normalize_path(path)
1408        try:
1409            return abspath(path)
1410        except ValueError:  # http://ironpython.codeplex.com/workitem/29489
1411            return path
1412
1413    def _fail(self, *messages):
1414        raise AssertionError(next(msg for msg in messages if msg))
1415
1416    def _error(self, msg):
1417        raise RuntimeError(msg)
1418
1419    def _info(self, msg):
1420        self._log(msg, 'INFO')
1421
1422    def _link(self, msg, *paths):
1423        paths = tuple('<a href="file://%s">%s</a>' % (p, p) for p in paths)
1424        self._log(msg % paths, 'HTML')
1425
1426    def _warn(self, msg):
1427        self._log(msg, 'WARN')
1428
1429    def _log(self, msg, level):
1430        logger.write(msg, level)
1431
1432
1433class _Process:
1434
1435    def __init__(self, command):
1436        self._command = self._process_command(command)
1437        self._process = os.popen(self._command)
1438
1439    def __str__(self):
1440        return self._command
1441
1442    def read(self):
1443        return self._process_output(self._process.read())
1444
1445    def close(self):
1446        try:
1447            rc = self._process.close()
1448        except IOError:  # Has occurred sometimes in Windows
1449            return 255
1450        if rc is None:
1451            return 0
1452        # In Windows (Python and Jython) return code is value returned by
1453        # command (can be almost anything)
1454        # In other OS:
1455        #   In Jython return code can be between '-255' - '255'
1456        #   In Python return code must be converted with 'rc >> 8' and it is
1457        #   between 0-255 after conversion
1458        if WINDOWS or JYTHON:
1459            return rc % 256
1460        return rc >> 8
1461
1462    def _process_command(self, command):
1463        if '>' not in command:
1464            if command.endswith('&'):
1465                command = command[:-1] + ' 2>&1 &'
1466            else:
1467                command += ' 2>&1'
1468        return self._encode_to_file_system(command)
1469
1470    def _encode_to_file_system(self, string):
1471        enc = sys.getfilesystemencoding() if PY2 else None
1472        return string.encode(enc) if enc else string
1473
1474    def _process_output(self, output):
1475        if '\r\n' in output:
1476            output = output.replace('\r\n', '\n')
1477        if output.endswith('\n'):
1478            output = output[:-1]
1479        return console_decode(output, force=True)
1480