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