blob: b6851ec8ce27b3ca9adcc01ed91f1beb35ed4b21 [file] [log] [blame]
Michael Walshde791732016-09-06 14:25:24 -05001#!/usr/bin/env python
2
Michael Walsh7423c012016-10-04 10:27:21 -05003r"""
4This module provides many valuable functions such as my_parm_file.
5"""
Michael Walshde791732016-09-06 14:25:24 -05006
7# sys and os are needed to get the program dir path and program name.
8import sys
Michael Walsheaa16852017-09-19 16:30:43 -05009import errno
Michael Walshde791732016-09-06 14:25:24 -050010import os
Michael Walshdece16c2018-08-07 15:01:05 -050011import collections
Michael Walsh6aa69802018-09-21 16:38:34 -050012import json
Michael Walshc9cb9722018-10-01 17:56:20 -050013import time
Michael Walshf5293d22019-02-01 14:23:02 -060014import inspect
Michael Walsh111a8312019-12-05 16:48:06 -060015import random
Michael Walshdece16c2018-08-07 15:01:05 -050016try:
17 import ConfigParser
18except ImportError:
19 import configparser
George Keishing3b7115a2018-08-02 10:48:17 -050020try:
Michael Walsh61f5e8f2018-08-03 11:16:00 -050021 import StringIO
George Keishing3b7115a2018-08-02 10:48:17 -050022except ImportError:
Michael Walshdece16c2018-08-07 15:01:05 -050023 import io
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060024import re
Michael Walsh1c85bab2017-05-04 14:29:24 -050025import socket
Michael Walsh3b621fe2018-07-24 16:27:53 -050026import tempfile
27try:
28 import psutil
29 psutil_imported = True
30except ImportError:
31 psutil_imported = False
Michael Walshde791732016-09-06 14:25:24 -050032
Michael Walsh7423c012016-10-04 10:27:21 -050033import gen_print as gp
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060034import gen_cmd as gc
35
Michael Walsh93a09f22017-11-13 15:34:46 -060036robot_env = gp.robot_env
37if robot_env:
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060038 from robot.libraries.BuiltIn import BuiltIn
Michael Walshdece16c2018-08-07 15:01:05 -050039 from robot.utils import DotDict
Michael Walsh7423c012016-10-04 10:27:21 -050040
Michael Walshde791732016-09-06 14:25:24 -050041
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060042def add_trailing_slash(dir_path):
Michael Walsh7db77942017-01-10 11:37:06 -060043 r"""
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060044 Add a trailing slash to the directory path if it doesn't already have one
45 and return it.
Michael Walsh7db77942017-01-10 11:37:06 -060046
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060047 Description of arguments:
48 dir_path A directory path.
Michael Walsh7db77942017-01-10 11:37:06 -060049 """
50
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060051 return os.path.normpath(dir_path) + os.path.sep
52
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060053
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060054def which(file_path):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060055 r"""
56 Find the full path of an executable file and return it.
57
58 The PATH environment variable dictates the results of this function.
59
60 Description of arguments:
Michael Walsh410b1782019-10-22 15:56:18 -050061 file_path The relative file path (e.g. "my_file" or "lib/my_file").
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060062 """
63
64 shell_rc, out_buf = gc.cmd_fnc_u("which " + file_path, quiet=1,
65 print_output=0, show_err=0)
66 if shell_rc != 0:
67 error_message = "Failed to find complete path for file \"" +\
68 file_path + "\".\n"
Michael Walsh0d5f96a2019-05-20 10:09:57 -050069 error_message += gp.sprint_var(shell_rc, gp.hexa())
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060070 error_message += out_buf
71 if robot_env:
72 BuiltIn().fail(gp.sprint_error(error_message))
73 else:
74 gp.print_error_report(error_message)
75 return False
76
77 file_path = out_buf.rstrip("\n")
78
79 return file_path
80
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060081
Michael Walshf7400f32018-09-26 17:13:43 -050082def add_path(new_path,
83 path,
84 position=0):
85 r"""
Michael Walsh410b1782019-10-22 15:56:18 -050086 Add new_path to path, provided that path doesn't already contain new_path, and return the result.
Michael Walshf7400f32018-09-26 17:13:43 -050087
88 Example:
89 If PATH has a value of "/bin/user:/lib/user". The following code:
90
91 PATH = add_path("/tmp/new_path", PATH)
92
93 will change PATH to "/tmp/new_path:/bin/user:/lib/user".
94
95 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -050096 new_path The path to be added. This function will strip the trailing slash.
97 path The path value to which the new_path should be added.
98 position The position in path where the new_path should be added. 0 means it
99 should be added to the beginning, 1 means add it as the 2nd item, etc.
100 sys.maxsize means it should be added to the end.
Michael Walshf7400f32018-09-26 17:13:43 -0500101 """
102
103 path_list = list(filter(None, path.split(":")))
104 new_path = new_path.rstrip("/")
105 if new_path not in path_list:
106 path_list.insert(int(position), new_path)
107 return ":".join(path_list)
108
109
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600110def dft(value, default):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600111 r"""
112 Return default if value is None. Otherwise, return value.
113
114 This is really just shorthand as shown below.
115
116 dft(value, default)
117
118 vs
119
120 default if value is None else value
121
122 Description of arguments:
123 value The value to be returned.
Michael Walsh410b1782019-10-22 15:56:18 -0500124 default The default value to return if value is None.
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600125 """
126
127 return default if value is None else value
128
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600129
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600130def get_mod_global(var_name,
131 default=None,
132 mod_name="__main__"):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600133 r"""
134 Get module global variable value and return it.
135
136 If we are running in a robot environment, the behavior will default to
137 calling get_variable_value.
138
139 Description of arguments:
Michael Walsh410b1782019-10-22 15:56:18 -0500140 var_name The name of the variable whose value is sought.
141 default The value to return if the global does not exist.
142 mod_name The name of the module containing the global variable.
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600143 """
144
145 if robot_env:
146 return BuiltIn().get_variable_value("${" + var_name + "}", default)
147
148 try:
149 module = sys.modules[mod_name]
150 except KeyError:
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500151 gp.print_error_report("Programmer error - The mod_name passed to"
152 + " this function is invalid:\n"
153 + gp.sprint_var(mod_name))
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600154 raise ValueError('Programmer error.')
155
156 if default is None:
157 return getattr(module, var_name)
158 else:
159 return getattr(module, var_name, default)
160
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600161
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600162def global_default(var_value,
163 default=0):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600164 r"""
165 If var_value is not None, return it. Otherwise, return the global
166 variable of the same name, if it exists. If not, return default.
167
168 This is meant for use by functions needing help assigning dynamic default
169 values to their parms. Example:
170
171 def func1(parm1=None):
172
173 parm1 = global_default(parm1, 0)
174
175 Description of arguments:
176 var_value The value being evaluated.
Michael Walsh410b1782019-10-22 15:56:18 -0500177 default The value to be returned if var_value is None AND the global variable of
178 the same name does not exist.
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600179 """
180
181 var_name = gp.get_arg_name(0, 1, stack_frame_ix=2)
182
183 return dft(var_value, get_mod_global(var_name, 0))
Michael Walsh7db77942017-01-10 11:37:06 -0600184
Michael Walsh7db77942017-01-10 11:37:06 -0600185
Michael Walsh7db77942017-01-10 11:37:06 -0600186def set_mod_global(var_value,
187 mod_name="__main__",
188 var_name=None):
Michael Walsh7db77942017-01-10 11:37:06 -0600189 r"""
190 Set a global variable for a given module.
191
192 Description of arguments:
193 var_value The value to set in the variable.
Michael Walsh410b1782019-10-22 15:56:18 -0500194 mod_name The name of the module whose variable is to be set.
195 var_name The name of the variable to set. This defaults to the name of the
196 variable used for var_value when calling this function.
Michael Walsh7db77942017-01-10 11:37:06 -0600197 """
198
199 try:
200 module = sys.modules[mod_name]
201 except KeyError:
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500202 gp.print_error_report("Programmer error - The mod_name passed to"
203 + " this function is invalid:\n"
204 + gp.sprint_var(mod_name))
Michael Walsh7db77942017-01-10 11:37:06 -0600205 raise ValueError('Programmer error.')
206
207 if var_name is None:
208 var_name = gp.get_arg_name(None, 1, 2)
209
210 setattr(module, var_name, var_value)
211
Michael Walsh7db77942017-01-10 11:37:06 -0600212
Michael Walshde791732016-09-06 14:25:24 -0500213def my_parm_file(prop_file_path):
Michael Walshde791732016-09-06 14:25:24 -0500214 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500215 Read a properties file, put the keys/values into a dictionary and return the dictionary.
Michael Walshde791732016-09-06 14:25:24 -0500216
217 The properties file must have the following format:
218 var_name<= or :>var_value
Michael Walsh410b1782019-10-22 15:56:18 -0500219 Comment lines (those beginning with a "#") and blank lines are allowed and will be ignored. Leading and
220 trailing single or double quotes will be stripped from the value. E.g.
Michael Walshde791732016-09-06 14:25:24 -0500221 var1="This one"
222 Quotes are stripped so the resulting value for var1 is:
223 This one
224
225 Description of arguments:
Michael Walsh410b1782019-10-22 15:56:18 -0500226 prop_file_path The caller should pass the path to the properties file.
Michael Walshde791732016-09-06 14:25:24 -0500227 """
228
Michael Walsh410b1782019-10-22 15:56:18 -0500229 # ConfigParser expects at least one section header in the file (or you get
230 # ConfigParser.MissingSectionHeaderError). Properties files don't need those so I'll write a dummy
231 # section header.
Michael Walshde791732016-09-06 14:25:24 -0500232
Michael Walshdece16c2018-08-07 15:01:05 -0500233 try:
234 string_file = StringIO.StringIO()
235 except NameError:
236 string_file = io.StringIO()
237
Michael Walshde791732016-09-06 14:25:24 -0500238 # Write the dummy section header to the string file.
239 string_file.write('[dummysection]\n')
240 # Write the entire contents of the properties file to the string file.
241 string_file.write(open(prop_file_path).read())
242 # Rewind the string file.
243 string_file.seek(0, os.SEEK_SET)
244
245 # Create the ConfigParser object.
Michael Walshdece16c2018-08-07 15:01:05 -0500246 try:
247 config_parser = ConfigParser.ConfigParser()
248 except NameError:
George Keishing36efbc02018-12-12 10:18:23 -0600249 config_parser = configparser.ConfigParser(strict=False)
Michael Walshde791732016-09-06 14:25:24 -0500250 # Make the property names case-sensitive.
251 config_parser.optionxform = str
252 # Read the properties from the string file.
253 config_parser.readfp(string_file)
254 # Return the properties as a dictionary.
Michael Walshdece16c2018-08-07 15:01:05 -0500255 if robot_env:
256 return DotDict(config_parser.items('dummysection'))
257 else:
258 return collections.OrderedDict(config_parser.items('dummysection'))
Michael Walshde791732016-09-06 14:25:24 -0500259
Michael Walsh7423c012016-10-04 10:27:21 -0500260
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600261def file_to_list(file_path,
262 newlines=0,
263 comments=1,
264 trim=0):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600265 r"""
266 Return the contents of a file as a list. Each element of the resulting
267 list is one line from the file.
268
269 Description of arguments:
Michael Walsh410b1782019-10-22 15:56:18 -0500270 file_path The path to the file (relative or absolute).
271 newlines Include newlines from the file in the results.
272 comments Include comment lines and blank lines in the results. Comment lines are
273 any that begin with 0 or more spaces followed by the pound sign ("#").
274 trim Trim white space from the beginning and end of each line.
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600275 """
276
277 lines = []
278 file = open(file_path)
279 for line in file:
280 if not comments:
281 if re.match(r"[ ]*#|^$", line):
282 continue
283 if not newlines:
284 line = line.rstrip("\n")
285 if trim:
286 line = line.strip()
287 lines.append(line)
Michael Walsh1383f352018-09-27 16:25:54 -0500288 file.close()
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600289
290 return lines
291
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600292
Michael Walsh1383f352018-09-27 16:25:54 -0500293def file_to_str(*args, **kwargs):
294 r"""
295 Return the contents of a file as a string.
296
297 Description of arguments:
298 See file_to_list defined above for description of arguments.
299 """
300
301 return '\n'.join(file_to_list(*args, **kwargs))
302
303
Michael Walsh111a8312019-12-05 16:48:06 -0600304def append_file(file_path, buffer):
305 r"""
306 Append the data in buffer to the file named in file_path.
307
308 Description of argument(s):
309 file_path The path to a file (e.g. "/tmp/root/file1").
310 buffer The buffer of data to be written to the file (e.g. "this and that").
311 """
312
313 with open(file_path, "a") as file:
314 file.write(buffer)
315
316
Michael Walsh7423c012016-10-04 10:27:21 -0500317def return_path_list():
Michael Walsh7423c012016-10-04 10:27:21 -0500318 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500319 This function will split the PATH environment variable into a PATH_LIST and return it. Each element in
320 the list will be normalized and have a trailing slash added.
Michael Walsh7423c012016-10-04 10:27:21 -0500321 """
322
323 PATH_LIST = os.environ['PATH'].split(":")
324 PATH_LIST = [os.path.normpath(path) + os.sep for path in PATH_LIST]
325
326 return PATH_LIST
327
Michael Walsh7db77942017-01-10 11:37:06 -0600328
Michael Walsh9fac55c2017-09-29 16:53:56 -0500329def escape_bash_quotes(buffer):
Michael Walsh9fac55c2017-09-29 16:53:56 -0500330 r"""
331 Escape quotes in string and return it.
332
333 The escape style implemented will be for use on the bash command line.
334
335 Example:
336 That's all.
337
338 Result:
339 That'\''s all.
340
341 The result may then be single quoted on a bash command. Example:
342
343 echo 'That'\''s all.'
344
345 Description of argument(s):
346 buffer The string whose quotes are to be escaped.
347 """
348
349 return re.sub("\'", "\'\\\'\'", buffer)
350
351
Michael Walsh7db77942017-01-10 11:37:06 -0600352def quote_bash_parm(parm):
Michael Walsh7db77942017-01-10 11:37:06 -0600353 r"""
354 Return the bash command line parm with single quotes if they are needed.
355
356 Description of arguments:
357 parm The string to be quoted.
358 """
359
Michael Walsh410b1782019-10-22 15:56:18 -0500360 # If any of these characters are found in the parm string, then the string should be quoted. This list
361 # is by no means complete and should be expanded as needed by the developer of this function.
Michael Walsh9fc17c32019-01-21 14:49:10 -0600362 # Spaces
363 # Single or double quotes.
364 # Bash variables (therefore, any string with a "$" may need quoting).
365 # Glob characters: *, ?, []
366 # Extended Glob characters: +, @, !
367 # Bash brace expansion: {}
368 # Tilde expansion: ~
369 # Piped commands: |
370 # Bash re-direction: >, <
371 bash_special_chars = set(' \'"$*?[]+@!{}~|><')
Michael Walsh7db77942017-01-10 11:37:06 -0600372
373 if any((char in bash_special_chars) for char in parm):
Michael Walsh9fc17c32019-01-21 14:49:10 -0600374 return "'" + escape_bash_quotes(parm) + "'"
375
376 if parm == '':
377 parm = "''"
Michael Walsh7db77942017-01-10 11:37:06 -0600378
379 return parm
380
Michael Walsh1c85bab2017-05-04 14:29:24 -0500381
Michael Walsh74427232018-08-31 12:54:24 -0500382def get_host_name_ip(host=None,
Michael Walshf74b3e42018-01-10 11:11:54 -0600383 short_name=0):
Michael Walsh1c85bab2017-05-04 14:29:24 -0500384 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500385 Get the host name and the IP address for the given host and return them as a tuple.
Michael Walsh1c85bab2017-05-04 14:29:24 -0500386
387 Description of argument(s):
Michael Walshd1b6c702017-05-30 17:54:30 -0500388 host The host name or IP address to be obtained.
Michael Walsh410b1782019-10-22 15:56:18 -0500389 short_name Include the short host name in the returned tuple, i.e. return host, ip
390 and short_host.
Michael Walsh1c85bab2017-05-04 14:29:24 -0500391 """
392
Michael Walsh74427232018-08-31 12:54:24 -0500393 host = dft(host, socket.gethostname())
Michael Walshf74b3e42018-01-10 11:11:54 -0600394 host_name = socket.getfqdn(host)
Michael Walshd1b6c702017-05-30 17:54:30 -0500395 try:
396 host_ip = socket.gethostbyname(host)
397 except socket.gaierror as my_gaierror:
398 message = "Unable to obtain the host name for the following host:" +\
399 "\n" + gp.sprint_var(host)
400 gp.print_error_report(message)
401 raise my_gaierror
Michael Walsh1c85bab2017-05-04 14:29:24 -0500402
Michael Walshf74b3e42018-01-10 11:11:54 -0600403 if short_name:
404 host_short_name = host_name.split(".")[0]
405 return host_name, host_ip, host_short_name
406 else:
407 return host_name, host_ip
Michael Walsh1c85bab2017-05-04 14:29:24 -0500408
Michael Walsheaa16852017-09-19 16:30:43 -0500409
410def pid_active(pid):
Michael Walsheaa16852017-09-19 16:30:43 -0500411 r"""
412 Return true if pid represents an active pid and false otherwise.
413
414 Description of argument(s):
415 pid The pid whose status is being sought.
416 """
417
418 try:
419 os.kill(int(pid), 0)
420 except OSError as err:
421 if err.errno == errno.ESRCH:
422 # ESRCH == No such process
423 return False
424 elif err.errno == errno.EPERM:
425 # EPERM clearly means there's a process to deny access to
426 return True
427 else:
428 # According to "man 2 kill" possible error values are
429 # (EINVAL, EPERM, ESRCH)
430 raise
431
432 return True
Michael Walsh112c3592018-06-01 14:15:58 -0500433
434
435def to_signed(number,
Michael Walshdece16c2018-08-07 15:01:05 -0500436 bit_width=None):
Michael Walsh112c3592018-06-01 14:15:58 -0500437 r"""
438 Convert number to a signed number and return the result.
439
440 Examples:
441
442 With the following code:
443
444 var1 = 0xfffffffffffffff1
445 print_var(var1)
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500446 print_var(var1, hexa())
Michael Walsh112c3592018-06-01 14:15:58 -0500447 var1 = to_signed(var1)
448 print_var(var1)
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500449 print_var(var1, hexa())
Michael Walsh112c3592018-06-01 14:15:58 -0500450
451 The following is written to stdout:
452 var1: 18446744073709551601
453 var1: 0x00000000fffffffffffffff1
454 var1: -15
455 var1: 0xfffffffffffffff1
456
Michael Walsh410b1782019-10-22 15:56:18 -0500457 The same code but with var1 set to 0x000000000000007f produces the following:
Michael Walsh112c3592018-06-01 14:15:58 -0500458 var1: 127
459 var1: 0x000000000000007f
460 var1: 127
461 var1: 0x000000000000007f
462
463 Description of argument(s):
464 number The number to be converted.
Michael Walsh410b1782019-10-22 15:56:18 -0500465 bit_width The number of bits that defines a complete hex value. Typically, this
466 would be a multiple of 32.
Michael Walsh112c3592018-06-01 14:15:58 -0500467 """
468
Michael Walshdece16c2018-08-07 15:01:05 -0500469 if bit_width is None:
470 try:
471 bit_width = gp.bit_length(long(sys.maxsize)) + 1
472 except NameError:
473 bit_width = gp.bit_length(int(sys.maxsize)) + 1
474
Michael Walsh112c3592018-06-01 14:15:58 -0500475 if number < 0:
476 return number
477 neg_bit_mask = 2**(bit_width - 1)
478 if number & neg_bit_mask:
479 return ((2**bit_width) - number) * -1
480 else:
481 return number
Michael Walsh3b621fe2018-07-24 16:27:53 -0500482
483
484def get_child_pids(quiet=1):
485
486 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500487 Get and return a list of pids representing all first-generation processes that are the children of the
488 current process.
Michael Walsh3b621fe2018-07-24 16:27:53 -0500489
490 Example:
491
492 children = get_child_pids()
493 print_var(children)
494
495 Output:
496 children:
497 children[0]: 9123
498
499 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500500 quiet Display output to stdout detailing how this child pids are obtained.
Michael Walsh3b621fe2018-07-24 16:27:53 -0500501 """
502
503 if psutil_imported:
504 # If "import psutil" worked, find child pids using psutil.
505 current_process = psutil.Process()
506 return [x.pid for x in current_process.children(recursive=False)]
507 else:
508 # Otherwise, find child pids using shell commands.
509 print_output = not quiet
510
511 ps_cmd_buf = "ps --no-headers --ppid " + str(os.getpid()) +\
512 " -o pid,args"
Michael Walsh410b1782019-10-22 15:56:18 -0500513 # Route the output of ps to a temporary file for later grepping. Avoid using " | grep" in the ps
514 # command string because it creates yet another process which is of no interest to the caller.
Michael Walsh3b621fe2018-07-24 16:27:53 -0500515 temp = tempfile.NamedTemporaryFile()
516 temp_file_path = temp.name
517 gc.shell_cmd(ps_cmd_buf + " > " + temp_file_path,
518 print_output=print_output)
519 # Sample contents of the temporary file:
520 # 30703 sleep 2
Michael Walsh410b1782019-10-22 15:56:18 -0500521 # 30795 /bin/bash -c ps --no-headers --ppid 30672 -o pid,args > /tmp/tmpqqorWY
522 # Use egrep to exclude the "ps" process itself from the results collected with the prior shell_cmd
523 # invocation. Only the other children are of interest to the caller. Use cut on the grep results to
524 # obtain only the pid column.
Michael Walsh3b621fe2018-07-24 16:27:53 -0500525 rc, output = \
526 gc.shell_cmd("egrep -v '" + re.escape(ps_cmd_buf) + "' "
527 + temp_file_path + " | cut -c1-5",
528 print_output=print_output)
Michael Walsh410b1782019-10-22 15:56:18 -0500529 # Split the output buffer by line into a list. Strip each element of extra spaces and convert each
530 # element to an integer.
Michael Walsh3b621fe2018-07-24 16:27:53 -0500531 return map(int, map(str.strip, filter(None, output.split("\n"))))
Michael Walsh6aa69802018-09-21 16:38:34 -0500532
533
534def json_loads_multiple(buffer):
535 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500536 Convert the contents of the buffer to a JSON array, run json.loads() on it and return the result.
Michael Walsh6aa69802018-09-21 16:38:34 -0500537
538 The buffer is expected to contain one or more JSON objects.
539
540 Description of argument(s):
541 buffer A string containing several JSON objects.
542 """
543
Michael Walsh410b1782019-10-22 15:56:18 -0500544 # Any line consisting of just "}", which indicates the end of an object, should have a comma appended.
Michael Walsh6aa69802018-09-21 16:38:34 -0500545 regex = "([\\r\\n])[\\}]([\\r\\n])"
546 buffer = re.sub(regex, "\\1},\\2", buffer, 1)
Michael Walsh410b1782019-10-22 15:56:18 -0500547 # Remove the comma from after the final object and place the whole buffer inside square brackets.
Michael Walsh6aa69802018-09-21 16:38:34 -0500548 buffer = "[" + re.sub(",([\r\n])$", "\\1}", buffer, 1) + "]"
549 if gp.robot_env:
550 return json.loads(buffer, object_pairs_hook=DotDict)
551 else:
552 return json.loads(buffer, object_pairs_hook=collections.OrderedDict)
Michael Walshc9cb9722018-10-01 17:56:20 -0500553
554
555def file_date_time_stamp():
556 r"""
557 Return a date/time stamp in the following format: yymmdd.HHMMSS
558
Michael Walsh410b1782019-10-22 15:56:18 -0500559 This value is suitable for including in file names. Example file1.181001.171716.status
Michael Walshc9cb9722018-10-01 17:56:20 -0500560 """
561
562 return time.strftime("%y%m%d.%H%M%S", time.localtime(time.time()))
Michael Walshf5293d22019-02-01 14:23:02 -0600563
564
565def get_function_stack():
566 r"""
567 Return a list of all the function names currently in the call stack.
568
Michael Walsh410b1782019-10-22 15:56:18 -0500569 This function's name will be at offset 0. This function's caller's name will be at offset 1 and so on.
Michael Walshf5293d22019-02-01 14:23:02 -0600570 """
571
572 return [str(stack_frame[3]) for stack_frame in inspect.stack()]
Michael Walsh2ce1dba2019-02-05 19:29:28 +0000573
574
575def username():
576 r"""
577 Return the username for the current process.
578 """
579
580 username = os.environ.get("USER", "")
581 if username != "":
582 return username
583 user_num = str(os.geteuid())
584 try:
585 username = os.getlogin()
586 except OSError:
587 if user_num == "0":
588 username = "root"
589 else:
590 username = "?"
591
592 return username
Michael Walsh97081e82019-08-20 17:07:46 +0000593
594
595def version_tuple(version):
596 r"""
597 Convert the version string to a tuple and return it.
598
599 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500600 version A version string whose format is "n[.n]" (e.g. "3.6.3", "3", etc.).
Michael Walsh97081e82019-08-20 17:07:46 +0000601 """
602
603 return tuple(map(int, (version.split("."))))
604
605
Michael Walsh87353e32019-10-23 12:50:43 -0500606def get_python_version():
607 r"""
608 Get and return the python version.
609 """
610
611 sys_version = sys.version
612 # Strip out any revision code data (e.g. "3.6.3rc1" will become "3.6.3").
613 sys_version = re.sub("rc[^ ]+", "", sys_version).split(" ")[0]
614 # Remove any non-numerics, etc. (e.g. "2.7.15+" becomes ""2.7.15").
615 return re.sub("[^0-9\\.]", "", sys_version)
616
617
Michael Walsh97081e82019-08-20 17:07:46 +0000618python_version = \
Michael Walsh87353e32019-10-23 12:50:43 -0500619 version_tuple(get_python_version())
Michael Walsh97081e82019-08-20 17:07:46 +0000620ordered_dict_version = version_tuple("3.6")
Michael Walsh111a8312019-12-05 16:48:06 -0600621
622
623def create_temp_file_path(delim=":", suffix=""):
624 r"""
625 Create a temporary file path and return it.
626
627 This function is appropriate for users who with to create a temporary file and:
628 1) Have control over when and whether the file is deleted.
629 2) Have the name of the file indicate information such as program name, function name, line, pid, etc.
630 This can be an aid in debugging, cleanup, etc.
631
632 The dir path portion of the file path will be /tmp/<username>/. This function will create this directory
633 if it doesn't already exist.
634
635 This function will NOT create the file. The file will NOT automatically get deleted. It is the
636 responsibility of the caller to dispose of it.
637
638 Example:
639
640 pgm123.py is run by user 'joe'. It calls func1 which contains this code:
641
642 temp_file_path = create_temp_file_path(suffix='suffix1')
643 print_var(temp_file_path)
644
645 Output:
646
647 temp_file_path: /tmp/joe/pgm123.py:func1:line_55:pid_8199:831848:suffix1
648
649 Description of argument(s):
650 delim A delimiter to be used to separate the sub-components of the file name.
651 suffix A suffix to include as the last sub-component of the file name.
652 """
653
654 temp_dir_path = "/tmp/" + username() + "/"
655 try:
656 os.mkdir(temp_dir_path)
657 except FileExistsError:
658 pass
659
660 callers_stack_frame = inspect.stack()[1]
661 file_name_elements = \
662 [
663 gp.pgm_name, callers_stack_frame.function, "line_" + str(callers_stack_frame.lineno),
664 "pid_" + str(os.getpid()), str(random.randint(0, 1000000)), suffix
665 ]
666 temp_file_name = delim.join(file_name_elements)
667
668 temp_file_path = temp_dir_path + temp_file_name
669
670 return temp_file_path
Michael Walsh05a7a6f2020-02-12 15:02:53 -0600671
672
673def pause(message="Hit enter to continue..."):
674 r"""
675 Print the message, with time stamp, and pause until the user hits enter.
676
677 Description of argument(s):
678 message The message to be printed to stdout.
679 """
680 gp.print_time(message)
681 try:
682 input()
683 except SyntaxError:
684 pass
685
686 return