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