1#!/usr/bin/env python
2
3r"""
4This module provides many valuable print functions such as sprint_var,
5sprint_time, sprint_error, sprint_call_stack.
6"""
7
8import sys
9import os
10import time
11import inspect
12import re
13import grp
14import socket
15import argparse
16import __builtin__
17import logging
18import collections
19
20try:
21    robot_env = 1
22    from robot.utils import DotDict
23    from robot.utils import NormalizedDict
24    from robot.libraries.BuiltIn import BuiltIn
25except ImportError:
26    robot_env = 0
27
28import gen_arg as ga
29
30# Setting these variables for use both inside this module and by programs
31# importing this module.
32pgm_dir_path = sys.argv[0]
33pgm_name = os.path.basename(pgm_dir_path)
34pgm_dir_name = re.sub("/" + pgm_name, "", pgm_dir_path) + "/"
35
36
37# Some functions (e.g. sprint_pgm_header) have need of a program name value
38# that looks more like a valid variable name.  Therefore, we'll swap odd
39# characters like "." out for underscores.
40pgm_name_var_name = pgm_name.replace(".", "_")
41
42# Initialize global values used as defaults by print_time, print_var, etc.
43col1_indent = 0
44
45# Calculate default column width for print_var functions based on environment
46# variable settings.  The objective is to make the variable values line up
47# nicely with the time stamps.
48col1_width = 29
49if 'NANOSECONDS' in os.environ:
50    NANOSECONDS = os.environ['NANOSECONDS']
51else:
52    NANOSECONDS = 0
53
54if NANOSECONDS == "1":
55    col1_width = col1_width + 7
56
57if 'SHOW_ELAPSED_TIME' in os.environ:
58    SHOW_ELAPSED_TIME = os.environ['SHOW_ELAPSED_TIME']
59else:
60    SHOW_ELAPSED_TIME = 0
61
62if SHOW_ELAPSED_TIME == "1":
63    if NANOSECONDS == "1":
64        col1_width = col1_width + 14
65    else:
66        col1_width = col1_width + 7
67
68# Initialize some time variables used in module functions.
69start_time = time.time()
70sprint_time_last_seconds = start_time
71
72# The user can set environment variable "GEN_PRINT_DEBUG" to get debug output
73# from this module.
74gen_print_debug = int(os.environ.get('GEN_PRINT_DEBUG', 0))
75
76
77###############################################################################
78def sprint_func_name(stack_frame_ix=None):
79
80    r"""
81    Return the function name associated with the indicated stack frame.
82
83    Description of arguments:
84    stack_frame_ix                  The index of the stack frame whose
85                                    function name should be returned.  If the
86                                    caller does not specify a value, this
87                                    function will set the value to 1 which is
88                                    the index of the caller's stack frame.  If
89                                    the caller is the wrapper function
90                                    "print_func_name", this function will bump
91                                    it up by 1.
92    """
93
94    # If user specified no stack_frame_ix, we'll set it to a proper default
95    # value.
96    if stack_frame_ix is None:
97        func_name = sys._getframe().f_code.co_name
98        caller_func_name = sys._getframe(1).f_code.co_name
99        if func_name[1:] == caller_func_name:
100            stack_frame_ix = 2
101        else:
102            stack_frame_ix = 1
103
104    func_name = sys._getframe(stack_frame_ix).f_code.co_name
105
106    return func_name
107
108###############################################################################
109
110
111# get_arg_name is not a print function per se.  I have included it in this
112# module because it is used by sprint_var which is found in this module.
113###############################################################################
114def get_arg_name(var,
115                 arg_num=1,
116                 stack_frame_ix=1):
117
118    r"""
119    Return the "name" of an argument passed to a function.  This could be a
120    literal or a variable name.
121
122    Description of arguments:
123    var                             The variable whose name you want returned.
124    arg_num                         The arg number (1 through n) whose name
125                                    you wish to have returned.  This value
126                                    should not exceed the number of arguments
127                                    allowed by the target function.
128    stack_frame_ix                  The stack frame index of the target
129                                    function.  This value must be 1 or
130                                    greater.  1 would indicate get_arg_name's
131                                    stack frame.  2 would be the caller of
132                                    get_arg_name's stack frame, etc.
133
134    Example 1:
135
136    my_var = "mike"
137    var_name = get_arg_name(my_var)
138
139    In this example, var_name will receive the value "my_var".
140
141    Example 2:
142
143    def test1(var):
144        # Getting the var name of the first arg to this function, test1.
145        # Note, in this case, it doesn't matter what you pass as the first arg
146        # to get_arg_name since it is the caller's variable name that matters.
147        dummy = 1
148        arg_num = 1
149        stack_frame = 2
150        var_name = get_arg_name(dummy, arg_num, stack_frame)
151
152    # Mainline...
153
154    another_var = "whatever"
155    test1(another_var)
156
157    In this example, var_name will be set to "another_var".
158
159    """
160
161    # Note: I wish to avoid recursion so I refrain from calling any function
162    # that calls this function (i.e. sprint_var, valid_value, etc.).
163
164    # The user can set environment variable "GET_ARG_NAME_DEBUG" to get debug
165    # output from this function.
166    local_debug = int(os.environ.get('GET_ARG_NAME_DEBUG', 0))
167    # In addition to GET_ARG_NAME_DEBUG, the user can set environment
168    # variable "GET_ARG_NAME_SHOW_SOURCE" to have this function include source
169    # code in the debug output.
170    local_debug_show_source = int(
171        os.environ.get('GET_ARG_NAME_SHOW_SOURCE', 0))
172
173    if arg_num < 1:
174        print_error("Programmer error - Variable \"arg_num\" has an invalid" +
175                    " value of \"" + str(arg_num) + "\".  The value must be" +
176                    " an integer that is greater than 0.\n")
177        # What is the best way to handle errors?  Raise exception?  I'll
178        # revisit later.
179        return
180    if stack_frame_ix < 1:
181        print_error("Programmer error - Variable \"stack_frame_ix\" has an" +
182                    " invalid value of \"" + str(stack_frame_ix) + "\".  The" +
183                    " value must be an integer that is greater than or equal" +
184                    " to 1.\n")
185        return
186
187    if local_debug:
188        debug_indent = 2
189        print("")
190        print_dashes(0, 120)
191        print(sprint_func_name() + "() parms:")
192        print_varx("var", var, 0, debug_indent)
193        print_varx("arg_num", arg_num, 0, debug_indent)
194        print_varx("stack_frame_ix", stack_frame_ix, 0, debug_indent)
195        print("")
196        print_call_stack(debug_indent, 2)
197
198    for count in range(0, 2):
199        try:
200            frame, filename, cur_line_no, function_name, lines, index = \
201                inspect.stack()[stack_frame_ix]
202        except IndexError:
203            print_error("Programmer error - The caller has asked for" +
204                        " information about the stack frame at index \"" +
205                        str(stack_frame_ix) + "\".  However, the stack" +
206                        " only contains " + str(len(inspect.stack())) +
207                        " entries.  Therefore the stack frame index is out" +
208                        " of range.\n")
209            return
210        if filename != "<string>":
211            break
212        # filename of "<string>" may mean that the function in question was
213        # defined dynamically and therefore its code stack is inaccessible.
214        # This may happen with functions like "rqprint_var".  In this case,
215        # we'll increment the stack_frame_ix and try again.
216        stack_frame_ix += 1
217        if local_debug:
218            print("Adjusted stack_frame_ix...")
219            print_varx("stack_frame_ix", stack_frame_ix, 0, debug_indent)
220
221    called_func_name = sprint_func_name(stack_frame_ix)
222
223    module = inspect.getmodule(frame)
224
225    # Though I would expect inspect.getsourcelines(frame) to get all module
226    # source lines if the frame is "<module>", it doesn't do that.  Therefore,
227    # for this special case, I will do inspect.getsourcelines(module).
228    if function_name == "<module>":
229        source_lines, source_line_num =\
230            inspect.getsourcelines(module)
231        line_ix = cur_line_no - source_line_num - 1
232    else:
233        source_lines, source_line_num =\
234            inspect.getsourcelines(frame)
235        line_ix = cur_line_no - source_line_num
236
237    if local_debug:
238        print("\n  Variables retrieved from inspect.stack() function:")
239        print_varx("frame", frame, 0, debug_indent + 2)
240        print_varx("filename", filename, 0, debug_indent + 2)
241        print_varx("cur_line_no", cur_line_no, 0, debug_indent + 2)
242        print_varx("function_name", function_name, 0, debug_indent + 2)
243        print_varx("lines", lines, 0, debug_indent + 2)
244        print_varx("index", index, 0, debug_indent + 2)
245        print_varx("source_line_num", source_line_num, 0, debug_indent)
246        print_varx("line_ix", line_ix, 0, debug_indent)
247        if local_debug_show_source:
248            print_varx("source_lines", source_lines, 0, debug_indent)
249        print_varx("called_func_name", called_func_name, 0, debug_indent)
250
251    # Get a list of all functions defined for the module.  Note that this
252    # doesn't work consistently when _run_exitfuncs is at the top of the stack
253    # (i.e. if we're running an exit function).  I've coded a work-around
254    # below for this deficiency.
255    all_functions = inspect.getmembers(module, inspect.isfunction)
256
257    # Get called_func_id by searching for our function in the list of all
258    # functions.
259    called_func_id = None
260    for func_name, function in all_functions:
261        if func_name == called_func_name:
262            called_func_id = id(function)
263            break
264    # NOTE: The only time I've found that called_func_id can't be found is
265    # when we're running from an exit function.
266
267    # Look for other functions in module with matching id.
268    aliases = set([called_func_name])
269    for func_name, function in all_functions:
270        if func_name == called_func_name:
271            continue
272        func_id = id(function)
273        if func_id == called_func_id:
274            aliases.add(func_name)
275
276    # In most cases, my general purpose code above will find all aliases.
277    # However, for the odd case (i.e. running from exit function), I've added
278    # code to handle pvar, qpvar, dpvar, etc. aliases explicitly since they
279    # are defined in this module and used frequently.
280    # pvar is an alias for print_var.
281    aliases.add(re.sub("print_var", "pvar", called_func_name))
282
283    func_regex = ".*(" + '|'.join(aliases) + ")[ ]*\("
284
285    # Search backward through source lines looking for the calling function
286    # name.
287    found = False
288    for start_line_ix in range(line_ix, 0, -1):
289        # Skip comment lines.
290        if re.match(r"[ ]*#", source_lines[start_line_ix]):
291            continue
292        if re.match(func_regex, source_lines[start_line_ix]):
293            found = True
294            break
295    if not found:
296        print_error("Programmer error - Could not find the source line with" +
297                    " a reference to function \"" + called_func_name + "\".\n")
298        return
299
300    # Search forward through the source lines looking for a line with the
301    # same indentation as the start time.  The end of our composite line
302    # should be the line preceding that line.
303    start_indent = len(source_lines[start_line_ix]) -\
304        len(source_lines[start_line_ix].lstrip(' '))
305    end_line_ix = line_ix
306    for end_line_ix in range(line_ix + 1, len(source_lines)):
307        if source_lines[end_line_ix].strip() == "":
308            continue
309        line_indent = len(source_lines[end_line_ix]) -\
310            len(source_lines[end_line_ix].lstrip(' '))
311        if line_indent == start_indent:
312            end_line_ix -= 1
313            break
314
315    # Join the start line through the end line into a composite line.
316    composite_line = ''.join(map(str.strip,
317                             source_lines[start_line_ix:end_line_ix + 1]))
318
319    # arg_list_etc = re.sub(".*" + called_func_name, "", composite_line)
320    arg_list_etc = "(" + re.sub(func_regex, "", composite_line)
321    if local_debug:
322        print_varx("aliases", aliases, 0, debug_indent)
323        print_varx("func_regex", func_regex, 0, debug_indent)
324        print_varx("start_line_ix", start_line_ix, 0, debug_indent)
325        print_varx("end_line_ix", end_line_ix, 0, debug_indent)
326        print_varx("composite_line", composite_line, 0, debug_indent)
327        print_varx("arg_list_etc", arg_list_etc, 0, debug_indent)
328
329    # Parse arg list...
330    # Initialize...
331    nest_level = -1
332    arg_ix = 0
333    args_list = [""]
334    for ix in range(0, len(arg_list_etc)):
335        char = arg_list_etc[ix]
336        # Set the nest_level based on whether we've encounted a parenthesis.
337        if char == "(":
338            nest_level += 1
339            if nest_level == 0:
340                continue
341        elif char == ")":
342            nest_level -= 1
343            if nest_level < 0:
344                break
345
346        # If we reach a comma at base nest level, we are done processing an
347        # argument so we increment arg_ix and initialize a new args_list entry.
348        if char == "," and nest_level == 0:
349            arg_ix += 1
350            args_list.append("")
351            continue
352
353        # For any other character, we append it it to the current arg list
354        # entry.
355        args_list[arg_ix] += char
356
357    # Trim whitespace from each list entry.
358    args_list = [arg.strip() for arg in args_list]
359
360    if arg_num > len(args_list):
361        print_error("Programmer error - The caller has asked for the name of" +
362                    " argument number \"" + str(arg_num) + "\" but there " +
363                    "were only \"" + str(len(args_list)) + "\" args used:\n" +
364                    sprint_varx("args_list", args_list))
365        return
366
367    argument = args_list[arg_num - 1]
368
369    if local_debug:
370        print_varx("args_list", args_list, 0, debug_indent)
371        print_varx("argument", argument, 0, debug_indent)
372        print_dashes(0, 120)
373
374    return argument
375
376###############################################################################
377
378
379###############################################################################
380def sprint_time(buffer=""):
381
382    r"""
383    Return the time in the following format.
384
385    Example:
386
387    The following python code...
388
389    sys.stdout.write(sprint_time())
390    sys.stdout.write("Hi.\n")
391
392    Will result in the following type of output:
393
394    #(CDT) 2016/07/08 15:25:35 - Hi.
395
396    Example:
397
398    The following python code...
399
400    sys.stdout.write(sprint_time("Hi.\n"))
401
402    Will result in the following type of output:
403
404    #(CDT) 2016/08/03 17:12:05 - Hi.
405
406    The following environment variables will affect the formatting as
407    described:
408    NANOSECONDS                     This will cause the time stamps to be
409                                    precise to the microsecond (Yes, it
410                                    probably should have been named
411                                    MICROSECONDS but the convention was set
412                                    long ago so we're sticking with it).
413                                    Example of the output when environment
414                                    variable NANOSECONDS=1.
415
416    #(CDT) 2016/08/03 17:16:25.510469 - Hi.
417
418    SHOW_ELAPSED_TIME               This will cause the elapsed time to be
419                                    included in the output.  This is the
420                                    amount of time that has elapsed since the
421                                    last time this function was called.  The
422                                    precision of the elapsed time field is
423                                    also affected by the value of the
424                                    NANOSECONDS environment variable.  Example
425                                    of the output when environment variable
426                                    NANOSECONDS=0 and SHOW_ELAPSED_TIME=1.
427
428    #(CDT) 2016/08/03 17:17:40 -    0 - Hi.
429
430    Example of the output when environment variable NANOSECONDS=1 and
431    SHOW_ELAPSED_TIME=1.
432
433    #(CDT) 2016/08/03 17:18:47.317339 -    0.000046 - Hi.
434
435    Description of arguments.
436    buffer                          This will be appended to the formatted
437                                    time string.
438    """
439
440    global NANOSECONDS
441    global SHOW_ELAPSED_TIME
442    global sprint_time_last_seconds
443
444    seconds = time.time()
445    loc_time = time.localtime(seconds)
446    nanoseconds = "%0.6f" % seconds
447    pos = nanoseconds.find(".")
448    nanoseconds = nanoseconds[pos:]
449
450    time_string = time.strftime("#(%Z) %Y/%m/%d %H:%M:%S", loc_time)
451    if NANOSECONDS == "1":
452        time_string = time_string + nanoseconds
453
454    if SHOW_ELAPSED_TIME == "1":
455        cur_time_seconds = seconds
456        math_string = "%9.9f" % cur_time_seconds + " - " + "%9.9f" % \
457            sprint_time_last_seconds
458        elapsed_seconds = eval(math_string)
459        if NANOSECONDS == "1":
460            elapsed_seconds = "%11.6f" % elapsed_seconds
461        else:
462            elapsed_seconds = "%4i" % elapsed_seconds
463        sprint_time_last_seconds = cur_time_seconds
464        time_string = time_string + " - " + elapsed_seconds
465
466    return time_string + " - " + buffer
467
468###############################################################################
469
470
471###############################################################################
472def sprint_timen(buffer=""):
473
474    r"""
475    Append a line feed to the buffer, pass it to sprint_time and return the
476    result.
477    """
478
479    return sprint_time(buffer + "\n")
480
481###############################################################################
482
483
484###############################################################################
485def sprint_error(buffer=""):
486
487    r"""
488    Return a standardized error string.  This includes:
489      - A time stamp
490      - The "**ERROR**" string
491      - The caller's buffer string.
492
493    Example:
494
495    The following python code...
496
497    print(sprint_error("Oops.\n"))
498
499    Will result in the following type of output:
500
501    #(CDT) 2016/08/03 17:12:05 - **ERROR** Oops.
502
503    Description of arguments.
504    buffer                          This will be appended to the formatted
505                                    error string.
506    """
507
508    return sprint_time() + "**ERROR** " + buffer
509
510###############################################################################
511
512
513###############################################################################
514def sprint_varx(var_name,
515                var_value,
516                hex=0,
517                loc_col1_indent=col1_indent,
518                loc_col1_width=col1_width,
519                trailing_char="\n"):
520
521    r"""
522    Print the var name/value passed to it.  If the caller lets loc_col1_width
523    default, the printing lines up nicely with output generated by the
524    print_time functions.
525
526    Note that the sprint_var function (defined below) can be used to call this
527    function so that the programmer does not need to pass the var_name.
528    sprint_var will figure out the var_name.  The sprint_var function is the
529    one that would normally be used by the general user.
530
531    For example, the following python code:
532
533    first_name = "Mike"
534    print_time("Doing this...\n")
535    print_varx("first_name", first_name)
536    print_time("Doing that...\n")
537
538    Will generate output like this:
539
540    #(CDT) 2016/08/10 17:34:42.847374 -    0.001285 - Doing this...
541    first_name:                                       Mike
542    #(CDT) 2016/08/10 17:34:42.847510 -    0.000136 - Doing that...
543
544    This function recognizes several complex types of data such as dict, list
545    or tuple.
546
547    For example, the following python code:
548
549    my_dict = dict(one=1, two=2, three=3)
550    print_var(my_dict)
551
552    Will generate the following output:
553
554    my_dict:
555      my_dict[three]:                                 3
556      my_dict[two]:                                   2
557      my_dict[one]:                                   1
558
559    Description of arguments.
560    var_name                        The name of the variable to be printed.
561    var_value                       The value of the variable to be printed.
562    hex                             This indicates that the value should be
563                                    printed in hex format.  It is the user's
564                                    responsibility to ensure that a var_value
565                                    contains a valid hex number.  For string
566                                    var_values, this will be interpreted as
567                                    show_blanks which means that blank values
568                                    will be printed as "<blank>".  For dict
569                                    var_values, this will be interpreted as
570                                    terse format where keys are not repeated
571                                    in the output.
572    loc_col1_indent                 The number of spaces to indent the output.
573    loc_col1_width                  The width of the output column containing
574                                    the variable name.  The default value of
575                                    this is adjusted so that the var_value
576                                    lines up with text printed via the
577                                    print_time function.
578    trailing_char                   The character to be used at the end of the
579                                    returned string.  The default value is a
580                                    line feed.
581    """
582
583    # Determine the type
584    if type(var_value) in (int, float, bool, str, unicode) \
585       or var_value is None:
586        # The data type is simple in the sense that it has no subordinate
587        # parts.
588        # Adjust loc_col1_width.
589        loc_col1_width = loc_col1_width - loc_col1_indent
590        # See if the user wants the output in hex format.
591        if hex:
592            if type(var_value) not in (int, long):
593                value_format = "%s"
594                if var_value == "":
595                    var_value = "<blank>"
596            else:
597                value_format = "0x%08x"
598        else:
599            value_format = "%s"
600        format_string = "%" + str(loc_col1_indent) + "s%-" \
601            + str(loc_col1_width) + "s" + value_format + trailing_char
602        return format_string % ("", str(var_name) + ":", var_value)
603    else:
604        # The data type is complex in the sense that it has subordinate parts.
605        format_string = "%" + str(loc_col1_indent) + "s%s\n"
606        buffer = format_string % ("", var_name + ":")
607        loc_col1_indent += 2
608        try:
609            length = len(var_value)
610        except TypeError:
611            length = 0
612        ix = 0
613        loc_trailing_char = "\n"
614        type_is_dict = 0
615        if type(var_value) is dict:
616            type_is_dict = 1
617        try:
618            if type(var_value) is collections.OrderedDict:
619                type_is_dict = 1
620        except AttributeError:
621            pass
622        try:
623            if type(var_value) is DotDict:
624                type_is_dict = 1
625        except NameError:
626            pass
627        try:
628            if type(var_value) is NormalizedDict:
629                type_is_dict = 1
630        except NameError:
631            pass
632        if type_is_dict:
633            for key, value in var_value.iteritems():
634                ix += 1
635                if ix == length:
636                    loc_trailing_char = trailing_char
637                if hex:
638                    # Since hex is being used as a format type, we want it
639                    # turned off when processing integer dictionary values so
640                    # it is not interpreted as a hex indicator.
641                    loc_hex = not (type(value) is int)
642                    buffer += sprint_varx(key, value,
643                                          loc_hex, loc_col1_indent,
644                                          loc_col1_width,
645                                          loc_trailing_char)
646                else:
647                    buffer += sprint_varx(var_name + "[" + key + "]", value,
648                                          hex, loc_col1_indent, loc_col1_width,
649                                          loc_trailing_char)
650        elif type(var_value) in (list, tuple, set):
651            for key, value in enumerate(var_value):
652                ix += 1
653                if ix == length:
654                    loc_trailing_char = trailing_char
655                buffer += sprint_varx(var_name + "[" + str(key) + "]", value,
656                                      hex, loc_col1_indent, loc_col1_width,
657                                      loc_trailing_char)
658        elif type(var_value) is argparse.Namespace:
659            for key in var_value.__dict__:
660                ix += 1
661                if ix == length:
662                    loc_trailing_char = trailing_char
663                cmd_buf = "buffer += sprint_varx(var_name + \".\" + str(key)" \
664                          + ", var_value." + key + ", hex, loc_col1_indent," \
665                          + " loc_col1_width, loc_trailing_char)"
666                exec(cmd_buf)
667        else:
668            var_type = type(var_value).__name__
669            func_name = sys._getframe().f_code.co_name
670            var_value = "<" + var_type + " type not supported by " + \
671                        func_name + "()>"
672            value_format = "%s"
673            loc_col1_indent -= 2
674            # Adjust loc_col1_width.
675            loc_col1_width = loc_col1_width - loc_col1_indent
676            format_string = "%" + str(loc_col1_indent) + "s%-" \
677                + str(loc_col1_width) + "s" + value_format + trailing_char
678            return format_string % ("", str(var_name) + ":", var_value)
679
680        return buffer
681
682    return ""
683
684###############################################################################
685
686
687###############################################################################
688def sprint_var(*args):
689
690    r"""
691    Figure out the name of the first argument for you and then call
692    sprint_varx with it.  Therefore, the following 2 calls are equivalent:
693    sprint_varx("var1", var1)
694    sprint_var(var1)
695    """
696
697    # Get the name of the first variable passed to this function.
698    stack_frame = 2
699    caller_func_name = sprint_func_name(2)
700    if caller_func_name.endswith("print_var"):
701        stack_frame += 1
702    var_name = get_arg_name(None, 1, stack_frame)
703    return sprint_varx(var_name, *args)
704
705###############################################################################
706
707
708###############################################################################
709def sprint_vars(*args):
710
711    r"""
712    Sprint the values of one or more variables.
713
714    Description of args:
715    args:
716        If the first argument is an integer, it will be interpreted to be the
717        "indent" value.
718        If the second argument is an integer, it will be interpreted to be the
719        "col1_width" value.
720        If the third argument is an integer, it will be interpreted to be the
721        "hex" value.
722        All remaining parms are considered variable names which are to be
723        sprinted.
724    """
725
726    if len(args) == 0:
727        return
728
729    # Get the name of the first variable passed to this function.
730    stack_frame = 2
731    caller_func_name = sprint_func_name(2)
732    if caller_func_name.endswith("print_vars"):
733        stack_frame += 1
734
735    parm_num = 1
736
737    # Create list from args (which is a tuple) so that it can be modified.
738    args_list = list(args)
739
740    var_name = get_arg_name(None, parm_num, stack_frame)
741    # See if parm 1 is to be interpreted as "indent".
742    try:
743        if type(int(var_name)) is int:
744            indent = int(var_name)
745            args_list.pop(0)
746            parm_num += 1
747    except ValueError:
748        indent = 0
749
750    var_name = get_arg_name(None, parm_num, stack_frame)
751    # See if parm 1 is to be interpreted as "col1_width".
752    try:
753        if type(int(var_name)) is int:
754            loc_col1_width = int(var_name)
755            args_list.pop(0)
756            parm_num += 1
757    except ValueError:
758        loc_col1_width = col1_width
759
760    var_name = get_arg_name(None, parm_num, stack_frame)
761    # See if parm 1 is to be interpreted as "hex".
762    try:
763        if type(int(var_name)) is int:
764            hex = int(var_name)
765            args_list.pop(0)
766            parm_num += 1
767    except ValueError:
768        hex = 0
769
770    buffer = ""
771    for var_value in args_list:
772        var_name = get_arg_name(None, parm_num, stack_frame)
773        buffer += sprint_varx(var_name, var_value, hex, indent, loc_col1_width)
774        parm_num += 1
775
776    return buffer
777
778###############################################################################
779
780
781###############################################################################
782def lprint_varx(var_name,
783                var_value,
784                hex=0,
785                loc_col1_indent=col1_indent,
786                loc_col1_width=col1_width,
787                log_level=getattr(logging, 'INFO')):
788
789    r"""
790    Send sprint_varx output to logging.
791    """
792
793    logging.log(log_level, sprint_varx(var_name, var_value, hex,
794                loc_col1_indent, loc_col1_width, ""))
795
796###############################################################################
797
798
799###############################################################################
800def lprint_var(*args):
801
802    r"""
803    Figure out the name of the first argument for you and then call
804    lprint_varx with it.  Therefore, the following 2 calls are equivalent:
805    lprint_varx("var1", var1)
806    lprint_var(var1)
807    """
808
809    # Get the name of the first variable passed to this function.
810    stack_frame = 2
811    caller_func_name = sprint_func_name(2)
812    if caller_func_name.endswith("print_var"):
813        stack_frame += 1
814    var_name = get_arg_name(None, 1, stack_frame)
815    lprint_varx(var_name, *args)
816
817###############################################################################
818
819
820###############################################################################
821def sprint_dashes(indent=col1_indent,
822                  width=80,
823                  line_feed=1,
824                  char="-"):
825
826    r"""
827    Return a string of dashes to the caller.
828
829    Description of arguments:
830    indent                          The number of characters to indent the
831                                    output.
832    width                           The width of the string of dashes.
833    line_feed                       Indicates whether the output should end
834                                    with a line feed.
835    char                            The character to be repeated in the output
836                                    string.
837    """
838
839    width = int(width)
840    buffer = " " * int(indent) + char * width
841    if line_feed:
842        buffer += "\n"
843
844    return buffer
845
846###############################################################################
847
848
849###############################################################################
850def sindent(text="",
851            indent=0):
852
853    r"""
854    Pre-pend the specified number of characters to the text string (i.e.
855    indent it) and return it.
856
857    Description of arguments:
858    text                            The string to be indented.
859    indent                          The number of characters to indent the
860                                    string.
861    """
862
863    format_string = "%" + str(indent) + "s%s"
864    buffer = format_string % ("", text)
865
866    return buffer
867
868###############################################################################
869
870
871###############################################################################
872def sprint_call_stack(indent=0,
873                      stack_frame_ix=0):
874
875    r"""
876    Return a call stack report for the given point in the program with line
877    numbers, function names and function parameters and arguments.
878
879    Sample output:
880
881    -------------------------------------------------------------------------
882    Python function call stack
883
884    Line # Function name and arguments
885    ------ ------------------------------------------------------------------
886       424 sprint_call_stack ()
887         4 print_call_stack ()
888        31 func1 (last_name = 'walsh', first_name = 'mikey')
889        59 /tmp/scr5.py
890    -------------------------------------------------------------------------
891
892    Description of arguments:
893    indent                          The number of characters to indent each
894                                    line of output.
895    stack_frame_ix                  The index of the first stack frame which
896                                    is to be returned.
897    """
898
899    buffer = ""
900    buffer += sprint_dashes(indent)
901    buffer += sindent("Python function call stack\n\n", indent)
902    buffer += sindent("Line # Function name and arguments\n", indent)
903    buffer += sprint_dashes(indent, 6, 0) + " " + sprint_dashes(0, 73)
904
905    # Grab the current program stack.
906    current_stack = inspect.stack()
907
908    # Process each frame in turn.
909    format_string = "%6s %s\n"
910    ix = 0
911    for stack_frame in current_stack:
912        if ix < stack_frame_ix:
913            ix += 1
914            continue
915        # I want the line number shown to be the line where you find the line
916        # shown.
917        try:
918            line_num = str(current_stack[ix + 1][2])
919        except IndexError:
920            line_num = ""
921        func_name = str(stack_frame[3])
922        if func_name == "?":
923            # "?" is the name used when code is not in a function.
924            func_name = "(none)"
925
926        if func_name == "<module>":
927            # If the func_name is the "main" program, we simply get the
928            # command line call string.
929            func_and_args = ' '.join(sys.argv)
930        else:
931            # Get the program arguments.
932            arg_vals = inspect.getargvalues(stack_frame[0])
933            function_parms = arg_vals[0]
934            frame_locals = arg_vals[3]
935
936            args_list = []
937            for arg_name in function_parms:
938                # Get the arg value from frame locals.
939                arg_value = frame_locals[arg_name]
940                args_list.append(arg_name + " = " + repr(arg_value))
941            args_str = "(" + ', '.join(map(str, args_list)) + ")"
942
943            # Now we need to print this in a nicely-wrapped way.
944            func_and_args = func_name + " " + args_str
945
946        buffer += sindent(format_string % (line_num, func_and_args), indent)
947        ix += 1
948
949    buffer += sprint_dashes(indent)
950
951    return buffer
952
953###############################################################################
954
955
956###############################################################################
957def sprint_executing(stack_frame_ix=None):
958
959    r"""
960    Print a line indicating what function is executing and with what parameter
961    values.  This is useful for debugging.
962
963    Sample output:
964
965    #(CDT) 2016/08/25 17:54:27 - Executing: func1 (x = 1)
966
967    Description of arguments:
968    stack_frame_ix                  The index of the stack frame whose
969                                    function info should be returned.  If the
970                                    caller does not specify a value, this
971                                    function will set the value to 1 which is
972                                    the index of the caller's stack frame.  If
973                                    the caller is the wrapper function
974                                    "print_executing", this function will bump
975                                    it up by 1.
976    """
977
978    # If user wants default stack_frame_ix.
979    if stack_frame_ix is None:
980        func_name = sys._getframe().f_code.co_name
981        caller_func_name = sys._getframe(1).f_code.co_name
982        if caller_func_name.endswith(func_name[1:]):
983            stack_frame_ix = 2
984        else:
985            stack_frame_ix = 1
986
987    stack_frame = inspect.stack()[stack_frame_ix]
988
989    func_name = str(stack_frame[3])
990    if func_name == "?":
991        # "?" is the name used when code is not in a function.
992        func_name = "(none)"
993
994    if func_name == "<module>":
995        # If the func_name is the "main" program, we simply get the command
996        # line call string.
997        func_and_args = ' '.join(sys.argv)
998    else:
999        # Get the program arguments.
1000        arg_vals = inspect.getargvalues(stack_frame[0])
1001        function_parms = arg_vals[0]
1002        frame_locals = arg_vals[3]
1003
1004        args_list = []
1005        for arg_name in function_parms:
1006            # Get the arg value from frame locals.
1007            arg_value = frame_locals[arg_name]
1008            args_list.append(arg_name + " = " + repr(arg_value))
1009        args_str = "(" + ', '.join(map(str, args_list)) + ")"
1010
1011        # Now we need to print this in a nicely-wrapped way.
1012        func_and_args = func_name + " " + args_str
1013
1014    return sprint_time() + "Executing: " + func_and_args + "\n"
1015
1016###############################################################################
1017
1018
1019###############################################################################
1020def sprint_pgm_header(indent=0,
1021                      linefeed=1):
1022
1023    r"""
1024    Return a standardized header that programs should print at the beginning
1025    of the run.  It includes useful information like command line, pid,
1026    userid, program parameters, etc.
1027
1028    Description of arguments:
1029    indent                          The number of characters to indent each
1030                                    line of output.
1031    linefeed                        Indicates whether a line feed be included
1032                                    at the beginning and end of the report.
1033    """
1034
1035    loc_col1_width = col1_width + indent
1036
1037    buffer = ""
1038    if linefeed:
1039        buffer = "\n"
1040
1041    buffer += sindent(sprint_time() + "Running " + pgm_name + ".\n", indent)
1042    buffer += sindent(sprint_time() + "Program parameter values, etc.:\n\n",
1043                      indent)
1044    buffer += sprint_varx("command_line", ' '.join(sys.argv), 0, indent,
1045                          loc_col1_width)
1046    # We want the output to show a customized name for the pid and pgid but
1047    # we want it to look like a valid variable name.  Therefore, we'll use
1048    # pgm_name_var_name which was set when this module was imported.
1049    buffer += sprint_varx(pgm_name_var_name + "_pid", os.getpid(), 0, indent,
1050                          loc_col1_width)
1051    buffer += sprint_varx(pgm_name_var_name + "_pgid", os.getpgrp(), 0, indent,
1052                          loc_col1_width)
1053    userid_num = str(os.geteuid())
1054    try:
1055        username = os.getlogin()
1056    except OSError:
1057        if userid_num == "0":
1058            username = "root"
1059        else:
1060            username = "?"
1061    buffer += sprint_varx("uid", userid_num + " (" + username +
1062                          ")", 0, indent, loc_col1_width)
1063    buffer += sprint_varx("gid", str(os.getgid()) + " (" +
1064                          str(grp.getgrgid(os.getgid()).gr_name) + ")", 0,
1065                          indent, loc_col1_width)
1066    buffer += sprint_varx("host_name", socket.gethostname(), 0, indent,
1067                          loc_col1_width)
1068    try:
1069        DISPLAY = os.environ['DISPLAY']
1070    except KeyError:
1071        DISPLAY = ""
1072    buffer += sprint_varx("DISPLAY", DISPLAY, 0, indent,
1073                          loc_col1_width)
1074    # I want to add code to print caller's parms.
1075
1076    # __builtin__.arg_obj is created by the get_arg module function,
1077    # gen_get_options.
1078    try:
1079        buffer += ga.sprint_args(__builtin__.arg_obj, indent)
1080    except AttributeError:
1081        pass
1082
1083    if linefeed:
1084        buffer += "\n"
1085
1086    return buffer
1087
1088###############################################################################
1089
1090
1091###############################################################################
1092def sprint_error_report(error_text="\n",
1093                        indent=2):
1094
1095    r"""
1096    Return a string with a standardized report which includes the caller's
1097    error text, the call stack and the program header.
1098
1099    Description of args:
1100    error_text                      The error text to be included in the
1101                                    report.  The caller should include any
1102                                    needed linefeeds.
1103    indent                          The number of characters to indent each
1104                                    line of output.
1105    """
1106
1107    buffer = ""
1108    buffer += sprint_dashes(width=120, char="=")
1109    buffer += sprint_error(error_text)
1110    buffer += "\n"
1111    # Calling sprint_call_stack with stack_frame_ix of 0 causes it to show
1112    # itself and this function in the call stack.  This is not helpful to a
1113    # debugger and is therefore clutter.  We will adjust the stack_frame_ix to
1114    # hide that information.
1115    stack_frame_ix = 2
1116    caller_func_name = sprint_func_name(2)
1117    if caller_func_name.endswith("print_error_report"):
1118        stack_frame_ix += 1
1119    buffer += sprint_call_stack(indent, stack_frame_ix)
1120    buffer += sprint_pgm_header(indent)
1121    buffer += sprint_dashes(width=120, char="=")
1122
1123    return buffer
1124
1125###############################################################################
1126
1127
1128###############################################################################
1129def sprint_issuing(cmd_buf,
1130                   test_mode=0):
1131
1132    r"""
1133    Return a line indicating a command that the program is about to execute.
1134
1135    Sample output for a cmd_buf of "ls"
1136
1137    #(CDT) 2016/08/25 17:57:36 - Issuing: ls
1138
1139    Description of args:
1140    cmd_buf                         The command to be executed by caller.
1141    test_mode                       With test_mode set, your output will look
1142                                    like this:
1143
1144    #(CDT) 2016/08/25 17:57:36 - (test_mode) Issuing: ls
1145
1146    """
1147
1148    buffer = sprint_time()
1149    if test_mode:
1150        buffer += "(test_mode) "
1151    buffer += "Issuing: " + cmd_buf + "\n"
1152
1153    return buffer
1154
1155###############################################################################
1156
1157
1158###############################################################################
1159def sprint_pgm_footer():
1160
1161    r"""
1162    Return a standardized footer that programs should print at the end of the
1163    program run.  It includes useful information like total run time, etc.
1164    """
1165
1166    buffer = "\n" + sprint_time() + "Finished running " + pgm_name + ".\n\n"
1167
1168    total_time = time.time() - start_time
1169    total_time_string = "%0.6f" % total_time
1170
1171    buffer += sprint_varx(pgm_name_var_name + "_runtime", total_time_string)
1172    buffer += "\n"
1173
1174    return buffer
1175
1176###############################################################################
1177
1178
1179###############################################################################
1180def sprint(buffer=""):
1181
1182    r"""
1183    Simply return the user's buffer.  This function is used by the qprint and
1184    dprint functions defined dynamically below, i.e. it would not normally be
1185    called for general use.
1186
1187    Description of arguments.
1188    buffer                          This will be returned to the caller.
1189    """
1190
1191    return str(buffer)
1192
1193###############################################################################
1194
1195
1196###############################################################################
1197def sprintn(buffer=""):
1198
1199    r"""
1200    Simply return the user's buffer with a line feed.  This function is used
1201    by the qprint and dprint functions defined dynamically below, i.e. it
1202    would not normally be called for general use.
1203
1204    Description of arguments.
1205    buffer                          This will be returned to the caller.
1206    """
1207
1208    buffer = str(buffer) + "\n"
1209
1210    return buffer
1211
1212###############################################################################
1213
1214
1215###############################################################################
1216def gp_debug_print(buffer):
1217
1218    r"""
1219    Print buffer to stdout only if gen_print_debug is set.
1220
1221    This function is intended for use only by other functions in this module.
1222
1223    Description of arguments:
1224    buffer                          The string to be printed.
1225    """
1226
1227    if not gen_print_debug:
1228        return
1229
1230    if robot_env:
1231        BuiltIn().log_to_console(buffer)
1232    else:
1233        print(buffer)
1234
1235###############################################################################
1236
1237
1238###############################################################################
1239def gp_get_var_value(var_name,
1240                     default=1):
1241
1242    r"""
1243    Get the value of the named variable and return it.  If the variable is not
1244    defined, the default value is returned.
1245
1246    If we are in a robot environment, get_variable_value will be used.
1247    Otherwise, the __builtin__ version of the variable is returned.
1248
1249    This function is intended for use only by other functions in this module.
1250
1251    Description of arguments:
1252    var_name                        The name of the variable whose value is to
1253                                    be returned.
1254    default                         The value that is returned if var_name is
1255                                    not defined.
1256    """
1257
1258    if robot_env:
1259        var_value = int(BuiltIn().get_variable_value("${" + var_name + "}",
1260                        default))
1261    else:
1262        var_value = getattr(__builtin__, var_name, default)
1263
1264    return var_value
1265
1266###############################################################################
1267
1268
1269###############################################################################
1270# In the following section of code, we will dynamically create print versions
1271# for each of the sprint functions defined above.  So, for example, where we
1272# have an sprint_time() function defined above that returns the time to the
1273# caller in a string, we will create a corresponding print_time() function
1274# that will print that string directly to stdout.
1275
1276# It can be complicated to follow what's being created by the exec statements
1277# below.  Here is an example of the print_time() function that will be created:
1278
1279# def print_time(*args):
1280#     s_func = getattr(sys.modules[__name__], "sprint_time")
1281#     sys.stdout.write(s_func(*args))
1282#     sys.stdout.flush()
1283
1284# Here are comments describing the 3 lines in the body of the created function.
1285# Create a reference to the "s" version of the given function in s_func (e.g.
1286# if this function name is print_time, we want s_funcname to be "sprint_time").
1287# Call the "s" version of this function passing it all of our arguments.
1288# Write the result to stdout.
1289
1290# func_names contains a list of all print functions which should be created
1291# from their sprint counterparts.
1292func_names = ['print_time', 'print_timen', 'print_error', 'print_varx',
1293              'print_var', 'print_vars', 'print_dashes', 'indent',
1294              'print_call_stack', 'print_func_name', 'print_executing',
1295              'print_pgm_header', 'print_issuing', 'print_pgm_footer',
1296              'print_error_report', 'print', 'printn']
1297
1298# stderr_func_names is a list of functions whose output should go to stderr
1299# rather than stdout.
1300stderr_func_names = ['print_error', 'print_error_report']
1301
1302gp_debug_print("robot_env: " + str(robot_env))
1303for func_name in func_names:
1304    gp_debug_print("func_name: " + func_name)
1305    if func_name in stderr_func_names:
1306        output_stream = "stderr"
1307    else:
1308        output_stream = "stdout"
1309
1310    func_def_line = "def " + func_name + "(*args):"
1311    s_func_line = "    s_func = getattr(sys.modules[__name__], \"s" +\
1312        func_name + "\")"
1313    # Generate the code to do the printing.
1314    if robot_env:
1315        func_print_lines = \
1316            [
1317                "    BuiltIn().log_to_console(s_func(*args),"
1318                " stream='" + output_stream + "',"
1319                " no_newline=True)"
1320            ]
1321    else:
1322        func_print_lines = \
1323            [
1324                "    sys." + output_stream + ".write(s_func(*args))",
1325                "    sys." + output_stream + ".flush()"
1326            ]
1327
1328    # Create an array containing the lines of the function we wish to create.
1329    func_def = [func_def_line, s_func_line] + func_print_lines
1330    # We don't want to try to redefine the "print" function, thus the if
1331    # statement.
1332    if func_name != "print":
1333        pgm_definition_string = '\n'.join(func_def)
1334        gp_debug_print(pgm_definition_string)
1335        exec(pgm_definition_string)
1336
1337    # Insert a blank line which will be overwritten by the next several
1338    # definitions.
1339    func_def.insert(1, "")
1340
1341    # Define the "q" (i.e. quiet) version of the given print function.
1342    func_def[0] = "def q" + func_name + "(*args):"
1343    func_def[1] = "    if gp_get_var_value(\"quiet\", 0): return"
1344    pgm_definition_string = '\n'.join(func_def)
1345    gp_debug_print(pgm_definition_string)
1346    exec(pgm_definition_string)
1347
1348    # Define the "d" (i.e. debug) version of the given print function.
1349    func_def[0] = "def d" + func_name + "(*args):"
1350    func_def[1] = "    if not gp_get_var_value(\"debug\", 0): return"
1351    pgm_definition_string = '\n'.join(func_def)
1352    gp_debug_print(pgm_definition_string)
1353    exec(pgm_definition_string)
1354
1355    # Define the "l" (i.e. log) version of the given print function.
1356    func_def_line = "def l" + func_name + "(*args):"
1357    func_print_lines = \
1358        [
1359            "    logging.log(getattr(logging, 'INFO'), s_func(*args))"
1360        ]
1361
1362    func_def = [func_def_line, s_func_line] + func_print_lines
1363    if func_name != "print_varx" and func_name != "print_var":
1364        pgm_definition_string = '\n'.join(func_def)
1365        gp_debug_print(pgm_definition_string)
1366        exec(pgm_definition_string)
1367
1368    if func_name == "print" or func_name == "printn":
1369        gp_debug_print("")
1370        continue
1371
1372    # Create abbreviated aliases (e.g. spvar is an alias for sprint_var).
1373    alias = re.sub("print_", "p", func_name)
1374    prefixes = ["", "s", "q", "d", "l"]
1375    for prefix in prefixes:
1376        pgm_definition_string = prefix + alias + " = " + prefix + func_name
1377        gp_debug_print(pgm_definition_string)
1378        exec(pgm_definition_string)
1379
1380    gp_debug_print("")
1381
1382###############################################################################
1383