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