Test Failed
Push — master ( e380d0...f5671d )
by W
02:58
created

st2client/st2client/utils/terminal.py (1 issue)

1
# Licensed to the StackStorm, Inc ('StackStorm') under one or more
2
# contributor license agreements.  See the NOTICE file distributed with
3
# this work for additional information regarding copyright ownership.
4
# The ASF licenses this file to You under the Apache License, Version 2.0
5
# (the "License"); you may not use this file except in compliance with
6
# the License.  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
from __future__ import absolute_import
17
18
import os
19
import struct
20
import subprocess
21
import sys
22
23
from st2client.utils.color import format_status
24
25
DEFAULT_TERMINAL_SIZE_COLUMNS = 150
26
27
__all__ = [
28
    'DEFAULT_TERMINAL_SIZE_COLUMNS',
29
30
    'get_terminal_size_columns'
31
]
32
33
34
def get_terminal_size_columns(default=DEFAULT_TERMINAL_SIZE_COLUMNS):
35
    """
36
    Try to retrieve COLUMNS value of terminal size using various system specific approaches.
37
38
    If terminal size can't be retrieved, default value is returned.
39
40
    NOTE 1: COLUMNS environment variable is checked first, if the value is not set / available,
41
            other methods are tried.
42
43
    :rtype: ``int``
44
    :return: columns
45
    """
46
    # 1. Try COLUMNS environment variable first like in upstream Python 3 method -
47
    # https://github.com/python/cpython/blob/master/Lib/shutil.py#L1203
48
    # This way it's consistent with upstream implementation. In the past, our implementation
49
    # checked those variables at the end as a fall back.
50
    try:
51
        columns = os.environ['COLUMNS']
52
        return int(columns)
53
    except (KeyError, ValueError):
54
        pass
55
56
    def ioctl_GWINSZ(fd):
57
        import fcntl
58
        import termios
59
        # Return a tuple (lines, columns)
60
        return struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
61
62
    # 2. try stdin, stdout, stderr
63
    for fd in (0, 1, 2):
64
        try:
65
            return ioctl_GWINSZ(fd)[1]
66
        except Exception:
67
            pass
68
69
    # 3. try os.ctermid()
70
    try:
71
        fd = os.open(os.ctermid(), os.O_RDONLY)
72
        try:
73
            return ioctl_GWINSZ(fd)[1]
74
        finally:
75
            os.close(fd)
76
    except Exception:
77
        pass
78
79
    # 4. try `stty size`
80
    try:
81
        process = subprocess.Popen(['stty', 'size'],
82
                                   shell=False,
83
                                   stdout=subprocess.PIPE,
84
                                   stderr=open(os.devnull, 'w'))
85
        result = process.communicate()
86
        if process.returncode == 0:
87
            return tuple(int(x) for x in result[0].split())[1]
88
    except Exception:
89
        pass
90
91
    # 5. return default fallback value
92
    return default
93
94
95
class TaskIndicator(object):
96
    def __enter__(self):
97
        self.dirty = False
98
        return self
99
100
    def __exit__(self, type, value, traceback):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in type.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
101
        return self.close()
102
103
    def add_stage(self, status, name):
104
        self._write('\t[{:^20}] {}'.format(format_status(status), name))
105
106
    def update_stage(self, status, name):
107
        self._write('\t[{:^20}] {}'.format(format_status(status), name), override=True)
108
109
    def finish_stage(self, status, name):
110
        self._write('\t[{:^20}] {}'.format(format_status(status), name), override=True)
111
112
    def close(self):
113
        if self.dirty:
114
            self._write('\n')
115
116
    def _write(self, string, override=False):
117
        if override:
118
            sys.stdout.write('\r')
119
        else:
120
            sys.stdout.write('\n')
121
122
        sys.stdout.write(string)
123
        sys.stdout.flush()
124
125
        self.dirty = True
126