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