1"""
2Used to manage the outputter system. This package is the modular system used
3for managing outputters.
4"""
5
6
7import errno
8import io
9import logging
10import os
11import re
12import sys
13import traceback
14
15import salt.loader
16import salt.utils.files
17import salt.utils.platform
18import salt.utils.stringutils
19
20# Are you really sure !!!
21# dealing with unicode is not as simple as setting defaultencoding
22# which can break other python modules imported by salt in bad ways...
23# reloading sys is not either a good idea...
24# reload(sys)
25# sys.setdefaultencoding('utf-8')
26
27log = logging.getLogger(__name__)
28
29
30def try_printout(data, out, opts, **kwargs):
31    """
32    Safely get the string to print out, try the configured outputter, then
33    fall back to nested and then to raw
34    """
35    try:
36        printout = get_printout(out, opts)(data, **kwargs)
37        if printout is not None:
38            return printout.rstrip()
39    except (KeyError, AttributeError, TypeError):
40        log.debug(traceback.format_exc())
41        try:
42            printout = get_printout("nested", opts)(data, **kwargs)
43            if printout is not None:
44                return printout.rstrip()
45        except (KeyError, AttributeError, TypeError):
46            log.error("Nested output failed: ", exc_info=True)
47            printout = get_printout("raw", opts)(data, **kwargs)
48            if printout is not None:
49                return printout.rstrip()
50
51
52def get_progress(opts, out, progress):
53    """
54    Get the progress bar from the given outputter
55    """
56    return salt.loader.raw_mod(opts, out, "rawmodule", mod="output")[
57        "{}.progress_iter".format(out)
58    ](progress)
59
60
61def update_progress(opts, progress, progress_iter, out):
62    """
63    Update the progress iterator for the given outputter
64    """
65    # Look up the outputter
66    try:
67        progress_outputter = salt.loader.outputters(opts)[out]
68    except KeyError:  # Outputter is not loaded
69        log.warning("Progress outputter not available.")
70        return False
71    progress_outputter(progress, progress_iter)
72
73
74def progress_end(progress_iter):
75    try:
76        progress_iter.stop()
77    except Exception:  # pylint: disable=broad-except
78        pass
79    return None
80
81
82def display_output(data, out=None, opts=None, **kwargs):
83    """
84    Print the passed data using the desired output
85    """
86    if opts is None:
87        opts = {}
88    display_data = try_printout(data, out, opts, **kwargs)
89
90    output_filename = opts.get("output_file", None)
91    log.trace("data = %s", data)
92    try:
93        # output filename can be either '' or None
94        if output_filename:
95            if not hasattr(output_filename, "write"):
96                # pylint: disable=resource-leakage
97                ofh = salt.utils.files.fopen(output_filename, "a")
98                # pylint: enable=resource-leakage
99                fh_opened = True
100            else:
101                # Filehandle/file-like object
102                ofh = output_filename
103                fh_opened = False
104
105            try:
106                fdata = display_data
107                if isinstance(fdata, str):
108                    try:
109                        fdata = fdata.encode("utf-8")
110                    except (UnicodeDecodeError, UnicodeEncodeError):
111                        # try to let the stream write
112                        # even if we didn't encode it
113                        pass
114                if fdata:
115                    ofh.write(salt.utils.stringutils.to_str(fdata))
116                    ofh.write("\n")
117            finally:
118                if fh_opened:
119                    ofh.close()
120            return
121        if display_data:
122            salt.utils.stringutils.print_cli(display_data)
123    except OSError as exc:
124        # Only raise if it's NOT a broken pipe
125        if exc.errno != errno.EPIPE:
126            raise
127
128
129def get_printout(out, opts=None, **kwargs):
130    """
131    Return a printer function
132    """
133    if opts is None:
134        opts = {}
135
136    if "output" in opts and opts["output"] != "highstate":
137        # new --out option, but don't choke when using --out=highstate at CLI
138        # See Issue #29796 for more information.
139        out = opts["output"]
140
141    # Handle setting the output when --static is passed.
142    if not out and opts.get("static"):
143        if opts.get("output"):
144            out = opts["output"]
145        elif opts.get("fun", "").split(".")[0] == "state":
146            # --static doesn't have an output set at this point, but if we're
147            # running a state function and "out" hasn't already been set, we
148            # should set the out variable to "highstate". Otherwise state runs
149            # are set to "nested" below. See Issue #44556 for more information.
150            out = "highstate"
151
152    if out == "text":
153        out = "txt"
154    elif out is None or out == "":
155        out = "nested"
156    if opts.get("progress", False):
157        out = "progress"
158
159    opts.update(kwargs)
160    if "color" not in opts:
161
162        def is_pipe():
163            """
164            Check if sys.stdout is a pipe or not
165            """
166            try:
167                fileno = sys.stdout.fileno()
168            except (AttributeError, io.UnsupportedOperation):
169                fileno = -1  # sys.stdout is StringIO or fake
170            return not os.isatty(fileno)
171
172        if opts.get("force_color", False):
173            opts["color"] = True
174        elif (
175            opts.get("no_color", False) or is_pipe() or salt.utils.platform.is_windows()
176        ):
177            opts["color"] = False
178        else:
179            opts["color"] = True
180    else:
181        if opts.get("force_color", False):
182            opts["color"] = True
183        elif opts.get("no_color", False) or salt.utils.platform.is_windows():
184            opts["color"] = False
185        else:
186            pass
187
188    outputters = salt.loader.outputters(opts)
189    if out not in outputters:
190        # Since the grains outputter was removed we don't need to fire this
191        # error when old minions are asking for it
192        if out != "grains":
193            log.error(
194                "Invalid outputter %s specified, fall back to nested",
195                out,
196            )
197        return outputters["nested"]
198    return outputters[out]
199
200
201def out_format(data, out, opts=None, **kwargs):
202    """
203    Return the formatted outputter string for the passed data
204    """
205    return try_printout(data, out, opts, **kwargs)
206
207
208def string_format(data, out, opts=None, **kwargs):
209    """
210    Return the formatted outputter string, removing the ANSI escape sequences.
211    """
212    raw_output = try_printout(data, out, opts, **kwargs)
213    ansi_escape = re.compile(r"\x1b[^m]*m")
214    return ansi_escape.sub("", raw_output)
215
216
217def html_format(data, out, opts=None, **kwargs):
218    """
219    Return the formatted string as HTML.
220    """
221    ansi_escaped_string = string_format(data, out, opts, **kwargs)
222    return ansi_escaped_string.replace(" ", "&nbsp;").replace("\n", "<br />")
223
224
225def strip_esc_sequence(txt):
226    """
227    Replace ESC (ASCII 27/Oct 33) to prevent unsafe strings
228    from writing their own terminal manipulation commands
229    """
230    if isinstance(txt, str):
231        return txt.replace("\033", "?")
232    else:
233        return txt
234