1#!/usr/bin/env python
2
3r"""
4This file contains functions useful for printing to stdout from robot programs.
5"""
6
7import sys
8import re
9import os
10
11try:
12    from robot.utils import DotDict as my_ord_dict
13except ImportError:
14    from collections import OrderedDict as my_ord_dict
15
16import gen_print as gp
17
18from robot.libraries.BuiltIn import BuiltIn
19from robot.api import logger
20
21try:
22    # The user can set environment variable "GEN_ROBOT_PRINT_DEBUG" to get
23    # debug output from this module.
24    gen_robot_print_debug = int(os.environ['GEN_ROBOT_PRINT_DEBUG'])
25except KeyError:
26    gen_robot_print_debug = 0
27
28
29def rprint(buffer="",
30           stream="STDOUT"):
31    r"""
32    rprint stands for "Robot Print".  This keyword will print the user's
33    buffer to the console.  This keyword does not write a linefeed.  It is the
34    responsibility of the caller to include a line feed if desired.  This
35    keyword is essentially an alias for "Log to Console  <string>  <stream>".
36
37    Description of arguments:
38    buffer                          The value that is to written to stdout.
39    """
40
41    BuiltIn().log_to_console(gp.replace_passwords(str(buffer)),
42                             no_newline=True, stream=stream)
43
44
45def rprintn(buffer="",
46            stream='STDOUT'):
47    r"""
48    rprintn stands for "Robot print with linefeed".  This keyword will print
49    the user's buffer to the console along with a linefeed.  It is basically
50    an abbreviated form of "Log go Console  <string>  <stream>"
51
52    Description of arguments:
53    buffer                          The value that is to written to stdout.
54    """
55
56    BuiltIn().log_to_console(gp.replace_passwords(buffer), no_newline=False,
57                             stream=stream)
58
59
60def sprint_vars(*args):
61    r"""
62    sprint_vars stands for "String Print Vars".  This is a robot redefinition
63    of the sprint_vars function in gen_print.py.  Given a list of variable
64    names, this keyword will string print each variable name and value such
65    that the value lines up in the same column as messages printed with rptime.
66
67    Description of arguments:
68    args:
69        If the first argument is an integer, it will be interpreted to be the
70        "hex" value.
71        If the second argument is an integer, it will be interpreted to be the
72        "indent" value.
73        If the third argument is an integer, it will be interpreted to be the
74        "col1_width" value.
75        All remaining parms are considered variable names which are to be
76        sprinted.
77    """
78
79    if len(args) == 0:
80        return
81
82    # Create list from args (which is a tuple) so that it can be modified.
83    args_list = list(args)
84
85    # See if parm 1 is to be interpreted as "hex".
86    try:
87        if isinstance(int(args_list[0]), int):
88            hex = int(args_list[0])
89            args_list.pop(0)
90    except ValueError:
91        hex = 0
92
93    # See if parm 2 is to be interpreted as "indent".
94    try:
95        if isinstance(int(args_list[0]), int):
96            indent = int(args_list[0])
97            args_list.pop(0)
98    except ValueError:
99        indent = 0
100
101    # See if parm 3 is to be interpreted as "col1_width".
102    try:
103        if isinstance(int(args_list[0]), int):
104            loc_col1_width = int(args_list[0])
105            args_list.pop(0)
106    except ValueError:
107        loc_col1_width = gp.col1_width
108
109    buffer = ""
110    for var_name in args_list:
111        var_value = BuiltIn().get_variable_value("${" + str(var_name) + "}")
112        buffer += gp.sprint_varx(var_name, var_value, hex, indent,
113                                 loc_col1_width)
114
115    return buffer
116
117
118def sprint_pgm_header(indent=0):
119    r"""
120    Sprint a standardized header that robot programs should print at the
121    beginning of the run.  The header includes useful information like command
122    line, pid, userid, program parameters, etc.  Callers need to have declared
123    a global @{parm_list} variable which contains the names of all program
124    parameters.
125    """
126
127    # This function is deprecated since the caller may now call the gen_print
128    # version directly.
129    return gp.sprint_pgm_header(indent, linefeed=1)
130
131
132def sprint_error_report(error_text="\n"):
133    r"""
134    Print a standardized error report that robot programs should print on
135    failure.  The report includes useful information like error text, command
136    line, pid, userid, program parameters, etc.  Callers must have declared a
137    @{parm_list} variable which contains the names of all program parameters.
138    """
139
140    # This function is deprecated.  The caller is advised to call the
141    # gen_print version of this function directly.
142
143    return gp.sprint_error_report(error_text)
144
145
146def sprint_issuing_keyword(cmd_buf,
147                           test_mode=0):
148    r"""
149    Return a line indicating a robot command (i.e. keyword + args) that the
150    program is about to execute.
151
152    For example, for the following robot code...
153
154    @{cmd_buf}=  Set Variable  Set Environment Variable  VAR1  1
155    rdprint_issuing_keyword
156
157    The output would look something like this:
158
159    #(CDT) 2016/10/27 12:04:21 - Issuing: Set Environment Variable  VAR1  1
160
161    Description of args:
162    cmd_buf                         A list containing the keyword and
163                                    arguments to be run.
164    """
165
166    buffer = ""
167    cmd_buf_str = '  '.join([str(element) for element in cmd_buf])
168    buffer += gp.sprint_issuing(cmd_buf_str, int(test_mode))
169
170    return buffer
171
172
173def sprint_auto_vars(headers=0):
174    r"""
175    This keyword will string print all of the Automatic Variables described in
176    the Robot User's Guide using rprint_vars.
177
178    NOTE: Not all automatic variables are guaranteed to exist.
179
180    Description of arguments:
181    headers                         This indicates that a header and footer
182                                    should be printed.
183    """
184
185    buffer = ""
186    if int(headers) == 1:
187        buffer += gp.sprint_dashes()
188        buffer += "Automatic Variables:"
189
190    buffer += \
191        sprint_vars(
192            "TEST_NAME", "TEST_TAGS", "TEST_DOCUMENTATION", "TEST_STATUS",
193            "TEST_DOCUMENTATION", "TEST_STATUS", "TEST_MESSAGE",
194            "PREV_TEST_NAME", "PREV_TEST_STATUS", "PREV_TEST_MESSAGE",
195            "SUITE_NAME", "SUITE_SOURCE", "SUITE_DOCUMENTATION",
196            "SUITE_METADATA", "SUITE_STATUS", "SUITE_MESSAGE",
197            "KEYWORD_STATUS", "KEYWORD_MESSAGE", "LOG_LEVEL", "OUTPUT_FILE",
198            "LOG_FILE", "REPORT_FILE", "DEBUG_FILE", "OUTPUT_DIR")
199
200    if int(headers) == 1:
201        buffer += gp.sprint_dashes()
202
203    return buffer
204
205
206# In the following section of code, we will dynamically create robot versions
207# of print functions for each of the sprint functions defined in the
208# gen_print.py module.  So, for example, where we have an sprint_time()
209# function defined above that returns the time to the caller in a string, we
210# will create a corresponding rprint_time() function that will print that
211# string directly to stdout.
212
213# It can be complicated to follow what's being created by the exec statement
214# below.  Here is an example of the rprint_time() function that will be
215# created (as of the time of this writing):
216
217# def rprint_time(*args):
218#   s_func = getattr(gp, "sprint_time")
219#   BuiltIn().log_to_console(s_func(*args),
220#                            stream='STDIN',
221#                            no_newline=True)
222
223# Here are comments describing the lines in the body of the created function.
224# Put a reference to the "s" version of this function in s_func.
225# Call the "s" version of this function passing it all of our arguments.  Log
226# the result to the console.
227
228robot_prefix = "r"
229robot_func_names =\
230    [
231        'print_error_report', 'print_pgm_header',
232        'print_issuing_keyword', 'print_vars', 'print_auto_vars'
233    ]
234func_names = gp.func_names + robot_func_names
235
236explicit_definitions = ['print', 'printn']
237
238func_names = list(my_ord_dict.fromkeys(func_names))
239
240if gen_robot_print_debug:
241    rprintn()
242    BuiltIn().log_to_console(gp.sprint_var(func_names), no_newline=True)
243    rprintn()
244
245for func_name in func_names:
246
247    if func_name not in explicit_definitions:
248        # The print_var function's job is to figure out the name of arg 1 and
249        # then call print_varx.  This is not currently supported for robot
250        # programs.  Though it IS supported for python modules.
251        if func_name == "print_error" or func_name == "print_error_report":
252            output_stream = "STDERR"
253        else:
254            output_stream = "STDIN"
255        if func_name in robot_func_names:
256            object_name = "__import__(__name__)"
257        else:
258            object_name = "gp"
259        func_def = \
260            [
261                "def " + robot_prefix + func_name + "(*args):",
262                "    s_func = getattr(" + object_name + ", \"s" + func_name
263                + "\")",
264                "    BuiltIn().log_to_console"
265                + "(gp.replace_passwords(s_func(*args)),"
266                " stream='" + output_stream + "',"
267                " no_newline=True)"
268            ]
269
270        pgm_definition_string = '\n'.join(func_def)
271        if gen_robot_print_debug:
272            rprintn()
273            rprintn(pgm_definition_string)
274        exec(pgm_definition_string)
275
276    # Now define "q" versions of each print function.  The q functions only
277    # print if global robot var "quiet" is 0.  If the global var quiet is not
278    # defined, it will be treated as though it were "1", i.e. no printing will
279    # be done.
280    func_def = \
281        [
282            "def rq" + func_name + "(*args):",
283            "    if int(gp.get_var_value(None, 0, \"quiet\")): return",
284            "    r" + func_name + "(*args)"
285        ]
286
287    pgm_definition_string = '\n'.join(func_def)
288    if gen_robot_print_debug:
289        rprintn(pgm_definition_string)
290    exec(pgm_definition_string)
291
292    # Now define "d" versions of each print function.  The d functions only
293    # print if global robot var "debug" is 1.
294    func_def = \
295        [
296            "def rd" + func_name + "(*args):",
297            "    if not int(gp.get_var_value(None, 0, \"debug\")): return",
298            "    r" + func_name + "(*args)"
299        ]
300
301    pgm_definition_string = '\n'.join(func_def)
302    if gen_robot_print_debug:
303        rprintn(pgm_definition_string)
304    exec(pgm_definition_string)
305
306    # Create shorter aliases.
307    prefixes = ["", "q", "d"]
308    alias = re.sub("print_", "p", func_name)
309    for prefix2 in prefixes:
310        cmd_buf = robot_prefix + prefix2 + alias + " = " + robot_prefix +\
311            prefix2 + func_name
312        if gen_robot_print_debug:
313            rprintn(cmd_buf)
314        exec(cmd_buf)
315
316# Define an alias.  rpvar is just a special case of rpvars where the args
317# list contains only one element.
318cmd_buf = "rpvar = rpvars"
319if gen_robot_print_debug:
320    rprintn()
321    rprintn(cmd_buf)
322exec(cmd_buf)
323