blob: 19c3d6e30215d34206ab7ec1901f58880dac385c [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 Walshdece16c2018-08-07 15:01:05 -050015try:
16 import ConfigParser
17except ImportError:
18 import configparser
George Keishing3b7115a2018-08-02 10:48:17 -050019try:
Michael Walsh61f5e8f2018-08-03 11:16:00 -050020 import StringIO
George Keishing3b7115a2018-08-02 10:48:17 -050021except ImportError:
Michael Walshdece16c2018-08-07 15:01:05 -050022 import io
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060023import re
Michael Walsh1c85bab2017-05-04 14:29:24 -050024import socket
Michael Walsh3b621fe2018-07-24 16:27:53 -050025import tempfile
26try:
27 import psutil
28 psutil_imported = True
29except ImportError:
30 psutil_imported = False
Michael Walshde791732016-09-06 14:25:24 -050031
Michael Walsh7423c012016-10-04 10:27:21 -050032import gen_print as gp
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060033import gen_cmd as gc
34
Michael Walsh93a09f22017-11-13 15:34:46 -060035robot_env = gp.robot_env
36if robot_env:
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060037 from robot.libraries.BuiltIn import BuiltIn
Michael Walshdece16c2018-08-07 15:01:05 -050038 from robot.utils import DotDict
Michael Walsh7423c012016-10-04 10:27:21 -050039
Michael Walshde791732016-09-06 14:25:24 -050040
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060041def add_trailing_slash(dir_path):
Michael Walsh7db77942017-01-10 11:37:06 -060042 r"""
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060043 Add a trailing slash to the directory path if it doesn't already have one
44 and return it.
Michael Walsh7db77942017-01-10 11:37:06 -060045
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060046 Description of arguments:
47 dir_path A directory path.
Michael Walsh7db77942017-01-10 11:37:06 -060048 """
49
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060050 return os.path.normpath(dir_path) + os.path.sep
51
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060052
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060053def which(file_path):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060054 r"""
55 Find the full path of an executable file and return it.
56
57 The PATH environment variable dictates the results of this function.
58
59 Description of arguments:
Michael Walsh410b1782019-10-22 15:56:18 -050060 file_path The relative file path (e.g. "my_file" or "lib/my_file").
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060061 """
62
63 shell_rc, out_buf = gc.cmd_fnc_u("which " + file_path, quiet=1,
64 print_output=0, show_err=0)
65 if shell_rc != 0:
66 error_message = "Failed to find complete path for file \"" +\
67 file_path + "\".\n"
Michael Walsh0d5f96a2019-05-20 10:09:57 -050068 error_message += gp.sprint_var(shell_rc, gp.hexa())
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060069 error_message += out_buf
70 if robot_env:
71 BuiltIn().fail(gp.sprint_error(error_message))
72 else:
73 gp.print_error_report(error_message)
74 return False
75
76 file_path = out_buf.rstrip("\n")
77
78 return file_path
79
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060080
Michael Walshf7400f32018-09-26 17:13:43 -050081def add_path(new_path,
82 path,
83 position=0):
84 r"""
Michael Walsh410b1782019-10-22 15:56:18 -050085 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 -050086
87 Example:
88 If PATH has a value of "/bin/user:/lib/user". The following code:
89
90 PATH = add_path("/tmp/new_path", PATH)
91
92 will change PATH to "/tmp/new_path:/bin/user:/lib/user".
93
94 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -050095 new_path The path to be added. This function will strip the trailing slash.
96 path The path value to which the new_path should be added.
97 position The position in path where the new_path should be added. 0 means it
98 should be added to the beginning, 1 means add it as the 2nd item, etc.
99 sys.maxsize means it should be added to the end.
Michael Walshf7400f32018-09-26 17:13:43 -0500100 """
101
102 path_list = list(filter(None, path.split(":")))
103 new_path = new_path.rstrip("/")
104 if new_path not in path_list:
105 path_list.insert(int(position), new_path)
106 return ":".join(path_list)
107
108
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600109def dft(value, default):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600110 r"""
111 Return default if value is None. Otherwise, return value.
112
113 This is really just shorthand as shown below.
114
115 dft(value, default)
116
117 vs
118
119 default if value is None else value
120
121 Description of arguments:
122 value The value to be returned.
Michael Walsh410b1782019-10-22 15:56:18 -0500123 default The default value to return if value is None.
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600124 """
125
126 return default if value is None else value
127
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600128
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600129def get_mod_global(var_name,
130 default=None,
131 mod_name="__main__"):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600132 r"""
133 Get module global variable value and return it.
134
135 If we are running in a robot environment, the behavior will default to
136 calling get_variable_value.
137
138 Description of arguments:
Michael Walsh410b1782019-10-22 15:56:18 -0500139 var_name The name of the variable whose value is sought.
140 default The value to return if the global does not exist.
141 mod_name The name of the module containing the global variable.
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600142 """
143
144 if robot_env:
145 return BuiltIn().get_variable_value("${" + var_name + "}", default)
146
147 try:
148 module = sys.modules[mod_name]
149 except KeyError:
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500150 gp.print_error_report("Programmer error - The mod_name passed to"
151 + " this function is invalid:\n"
152 + gp.sprint_var(mod_name))
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600153 raise ValueError('Programmer error.')
154
155 if default is None:
156 return getattr(module, var_name)
157 else:
158 return getattr(module, var_name, default)
159
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600160
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600161def global_default(var_value,
162 default=0):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600163 r"""
164 If var_value is not None, return it. Otherwise, return the global
165 variable of the same name, if it exists. If not, return default.
166
167 This is meant for use by functions needing help assigning dynamic default
168 values to their parms. Example:
169
170 def func1(parm1=None):
171
172 parm1 = global_default(parm1, 0)
173
174 Description of arguments:
175 var_value The value being evaluated.
Michael Walsh410b1782019-10-22 15:56:18 -0500176 default The value to be returned if var_value is None AND the global variable of
177 the same name does not exist.
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600178 """
179
180 var_name = gp.get_arg_name(0, 1, stack_frame_ix=2)
181
182 return dft(var_value, get_mod_global(var_name, 0))
Michael Walsh7db77942017-01-10 11:37:06 -0600183
Michael Walsh7db77942017-01-10 11:37:06 -0600184
Michael Walsh7db77942017-01-10 11:37:06 -0600185def set_mod_global(var_value,
186 mod_name="__main__",
187 var_name=None):
Michael Walsh7db77942017-01-10 11:37:06 -0600188 r"""
189 Set a global variable for a given module.
190
191 Description of arguments:
192 var_value The value to set in the variable.
Michael Walsh410b1782019-10-22 15:56:18 -0500193 mod_name The name of the module whose variable is to be set.
194 var_name The name of the variable to set. This defaults to the name of the
195 variable used for var_value when calling this function.
Michael Walsh7db77942017-01-10 11:37:06 -0600196 """
197
198 try:
199 module = sys.modules[mod_name]
200 except KeyError:
Joy Onyerikwu004ad3c2018-06-11 16:29:56 -0500201 gp.print_error_report("Programmer error - The mod_name passed to"
202 + " this function is invalid:\n"
203 + gp.sprint_var(mod_name))
Michael Walsh7db77942017-01-10 11:37:06 -0600204 raise ValueError('Programmer error.')
205
206 if var_name is None:
207 var_name = gp.get_arg_name(None, 1, 2)
208
209 setattr(module, var_name, var_value)
210
Michael Walsh7db77942017-01-10 11:37:06 -0600211
Michael Walshde791732016-09-06 14:25:24 -0500212def my_parm_file(prop_file_path):
Michael Walshde791732016-09-06 14:25:24 -0500213 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500214 Read a properties file, put the keys/values into a dictionary and return the dictionary.
Michael Walshde791732016-09-06 14:25:24 -0500215
216 The properties file must have the following format:
217 var_name<= or :>var_value
Michael Walsh410b1782019-10-22 15:56:18 -0500218 Comment lines (those beginning with a "#") and blank lines are allowed and will be ignored. Leading and
219 trailing single or double quotes will be stripped from the value. E.g.
Michael Walshde791732016-09-06 14:25:24 -0500220 var1="This one"
221 Quotes are stripped so the resulting value for var1 is:
222 This one
223
224 Description of arguments:
Michael Walsh410b1782019-10-22 15:56:18 -0500225 prop_file_path The caller should pass the path to the properties file.
Michael Walshde791732016-09-06 14:25:24 -0500226 """
227
Michael Walsh410b1782019-10-22 15:56:18 -0500228 # ConfigParser expects at least one section header in the file (or you get
229 # ConfigParser.MissingSectionHeaderError). Properties files don't need those so I'll write a dummy
230 # section header.
Michael Walshde791732016-09-06 14:25:24 -0500231
Michael Walshdece16c2018-08-07 15:01:05 -0500232 try:
233 string_file = StringIO.StringIO()
234 except NameError:
235 string_file = io.StringIO()
236
Michael Walshde791732016-09-06 14:25:24 -0500237 # Write the dummy section header to the string file.
238 string_file.write('[dummysection]\n')
239 # Write the entire contents of the properties file to the string file.
240 string_file.write(open(prop_file_path).read())
241 # Rewind the string file.
242 string_file.seek(0, os.SEEK_SET)
243
244 # Create the ConfigParser object.
Michael Walshdece16c2018-08-07 15:01:05 -0500245 try:
246 config_parser = ConfigParser.ConfigParser()
247 except NameError:
George Keishing36efbc02018-12-12 10:18:23 -0600248 config_parser = configparser.ConfigParser(strict=False)
Michael Walshde791732016-09-06 14:25:24 -0500249 # Make the property names case-sensitive.
250 config_parser.optionxform = str
251 # Read the properties from the string file.
252 config_parser.readfp(string_file)
253 # Return the properties as a dictionary.
Michael Walshdece16c2018-08-07 15:01:05 -0500254 if robot_env:
255 return DotDict(config_parser.items('dummysection'))
256 else:
257 return collections.OrderedDict(config_parser.items('dummysection'))
Michael Walshde791732016-09-06 14:25:24 -0500258
Michael Walsh7423c012016-10-04 10:27:21 -0500259
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600260def file_to_list(file_path,
261 newlines=0,
262 comments=1,
263 trim=0):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600264 r"""
265 Return the contents of a file as a list. Each element of the resulting
266 list is one line from the file.
267
268 Description of arguments:
Michael Walsh410b1782019-10-22 15:56:18 -0500269 file_path The path to the file (relative or absolute).
270 newlines Include newlines from the file in the results.
271 comments Include comment lines and blank lines in the results. Comment lines are
272 any that begin with 0 or more spaces followed by the pound sign ("#").
273 trim Trim white space from the beginning and end of each line.
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600274 """
275
276 lines = []
277 file = open(file_path)
278 for line in file:
279 if not comments:
280 if re.match(r"[ ]*#|^$", line):
281 continue
282 if not newlines:
283 line = line.rstrip("\n")
284 if trim:
285 line = line.strip()
286 lines.append(line)
Michael Walsh1383f352018-09-27 16:25:54 -0500287 file.close()
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600288
289 return lines
290
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600291
Michael Walsh1383f352018-09-27 16:25:54 -0500292def file_to_str(*args, **kwargs):
293 r"""
294 Return the contents of a file as a string.
295
296 Description of arguments:
297 See file_to_list defined above for description of arguments.
298 """
299
300 return '\n'.join(file_to_list(*args, **kwargs))
301
302
Michael Walsh7423c012016-10-04 10:27:21 -0500303def return_path_list():
Michael Walsh7423c012016-10-04 10:27:21 -0500304 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500305 This function will split the PATH environment variable into a PATH_LIST and return it. Each element in
306 the list will be normalized and have a trailing slash added.
Michael Walsh7423c012016-10-04 10:27:21 -0500307 """
308
309 PATH_LIST = os.environ['PATH'].split(":")
310 PATH_LIST = [os.path.normpath(path) + os.sep for path in PATH_LIST]
311
312 return PATH_LIST
313
Michael Walsh7db77942017-01-10 11:37:06 -0600314
Michael Walsh9fac55c2017-09-29 16:53:56 -0500315def escape_bash_quotes(buffer):
Michael Walsh9fac55c2017-09-29 16:53:56 -0500316 r"""
317 Escape quotes in string and return it.
318
319 The escape style implemented will be for use on the bash command line.
320
321 Example:
322 That's all.
323
324 Result:
325 That'\''s all.
326
327 The result may then be single quoted on a bash command. Example:
328
329 echo 'That'\''s all.'
330
331 Description of argument(s):
332 buffer The string whose quotes are to be escaped.
333 """
334
335 return re.sub("\'", "\'\\\'\'", buffer)
336
337
Michael Walsh7db77942017-01-10 11:37:06 -0600338def quote_bash_parm(parm):
Michael Walsh7db77942017-01-10 11:37:06 -0600339 r"""
340 Return the bash command line parm with single quotes if they are needed.
341
342 Description of arguments:
343 parm The string to be quoted.
344 """
345
Michael Walsh410b1782019-10-22 15:56:18 -0500346 # If any of these characters are found in the parm string, then the string should be quoted. This list
347 # is by no means complete and should be expanded as needed by the developer of this function.
Michael Walsh9fc17c32019-01-21 14:49:10 -0600348 # Spaces
349 # Single or double quotes.
350 # Bash variables (therefore, any string with a "$" may need quoting).
351 # Glob characters: *, ?, []
352 # Extended Glob characters: +, @, !
353 # Bash brace expansion: {}
354 # Tilde expansion: ~
355 # Piped commands: |
356 # Bash re-direction: >, <
357 bash_special_chars = set(' \'"$*?[]+@!{}~|><')
Michael Walsh7db77942017-01-10 11:37:06 -0600358
359 if any((char in bash_special_chars) for char in parm):
Michael Walsh9fc17c32019-01-21 14:49:10 -0600360 return "'" + escape_bash_quotes(parm) + "'"
361
362 if parm == '':
363 parm = "''"
Michael Walsh7db77942017-01-10 11:37:06 -0600364
365 return parm
366
Michael Walsh1c85bab2017-05-04 14:29:24 -0500367
Michael Walsh74427232018-08-31 12:54:24 -0500368def get_host_name_ip(host=None,
Michael Walshf74b3e42018-01-10 11:11:54 -0600369 short_name=0):
Michael Walsh1c85bab2017-05-04 14:29:24 -0500370 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500371 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 -0500372
373 Description of argument(s):
Michael Walshd1b6c702017-05-30 17:54:30 -0500374 host The host name or IP address to be obtained.
Michael Walsh410b1782019-10-22 15:56:18 -0500375 short_name Include the short host name in the returned tuple, i.e. return host, ip
376 and short_host.
Michael Walsh1c85bab2017-05-04 14:29:24 -0500377 """
378
Michael Walsh74427232018-08-31 12:54:24 -0500379 host = dft(host, socket.gethostname())
Michael Walshf74b3e42018-01-10 11:11:54 -0600380 host_name = socket.getfqdn(host)
Michael Walshd1b6c702017-05-30 17:54:30 -0500381 try:
382 host_ip = socket.gethostbyname(host)
383 except socket.gaierror as my_gaierror:
384 message = "Unable to obtain the host name for the following host:" +\
385 "\n" + gp.sprint_var(host)
386 gp.print_error_report(message)
387 raise my_gaierror
Michael Walsh1c85bab2017-05-04 14:29:24 -0500388
Michael Walshf74b3e42018-01-10 11:11:54 -0600389 if short_name:
390 host_short_name = host_name.split(".")[0]
391 return host_name, host_ip, host_short_name
392 else:
393 return host_name, host_ip
Michael Walsh1c85bab2017-05-04 14:29:24 -0500394
Michael Walsheaa16852017-09-19 16:30:43 -0500395
396def pid_active(pid):
Michael Walsheaa16852017-09-19 16:30:43 -0500397 r"""
398 Return true if pid represents an active pid and false otherwise.
399
400 Description of argument(s):
401 pid The pid whose status is being sought.
402 """
403
404 try:
405 os.kill(int(pid), 0)
406 except OSError as err:
407 if err.errno == errno.ESRCH:
408 # ESRCH == No such process
409 return False
410 elif err.errno == errno.EPERM:
411 # EPERM clearly means there's a process to deny access to
412 return True
413 else:
414 # According to "man 2 kill" possible error values are
415 # (EINVAL, EPERM, ESRCH)
416 raise
417
418 return True
Michael Walsh112c3592018-06-01 14:15:58 -0500419
420
421def to_signed(number,
Michael Walshdece16c2018-08-07 15:01:05 -0500422 bit_width=None):
Michael Walsh112c3592018-06-01 14:15:58 -0500423 r"""
424 Convert number to a signed number and return the result.
425
426 Examples:
427
428 With the following code:
429
430 var1 = 0xfffffffffffffff1
431 print_var(var1)
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500432 print_var(var1, hexa())
Michael Walsh112c3592018-06-01 14:15:58 -0500433 var1 = to_signed(var1)
434 print_var(var1)
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500435 print_var(var1, hexa())
Michael Walsh112c3592018-06-01 14:15:58 -0500436
437 The following is written to stdout:
438 var1: 18446744073709551601
439 var1: 0x00000000fffffffffffffff1
440 var1: -15
441 var1: 0xfffffffffffffff1
442
Michael Walsh410b1782019-10-22 15:56:18 -0500443 The same code but with var1 set to 0x000000000000007f produces the following:
Michael Walsh112c3592018-06-01 14:15:58 -0500444 var1: 127
445 var1: 0x000000000000007f
446 var1: 127
447 var1: 0x000000000000007f
448
449 Description of argument(s):
450 number The number to be converted.
Michael Walsh410b1782019-10-22 15:56:18 -0500451 bit_width The number of bits that defines a complete hex value. Typically, this
452 would be a multiple of 32.
Michael Walsh112c3592018-06-01 14:15:58 -0500453 """
454
Michael Walshdece16c2018-08-07 15:01:05 -0500455 if bit_width is None:
456 try:
457 bit_width = gp.bit_length(long(sys.maxsize)) + 1
458 except NameError:
459 bit_width = gp.bit_length(int(sys.maxsize)) + 1
460
Michael Walsh112c3592018-06-01 14:15:58 -0500461 if number < 0:
462 return number
463 neg_bit_mask = 2**(bit_width - 1)
464 if number & neg_bit_mask:
465 return ((2**bit_width) - number) * -1
466 else:
467 return number
Michael Walsh3b621fe2018-07-24 16:27:53 -0500468
469
470def get_child_pids(quiet=1):
471
472 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500473 Get and return a list of pids representing all first-generation processes that are the children of the
474 current process.
Michael Walsh3b621fe2018-07-24 16:27:53 -0500475
476 Example:
477
478 children = get_child_pids()
479 print_var(children)
480
481 Output:
482 children:
483 children[0]: 9123
484
485 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500486 quiet Display output to stdout detailing how this child pids are obtained.
Michael Walsh3b621fe2018-07-24 16:27:53 -0500487 """
488
489 if psutil_imported:
490 # If "import psutil" worked, find child pids using psutil.
491 current_process = psutil.Process()
492 return [x.pid for x in current_process.children(recursive=False)]
493 else:
494 # Otherwise, find child pids using shell commands.
495 print_output = not quiet
496
497 ps_cmd_buf = "ps --no-headers --ppid " + str(os.getpid()) +\
498 " -o pid,args"
Michael Walsh410b1782019-10-22 15:56:18 -0500499 # Route the output of ps to a temporary file for later grepping. Avoid using " | grep" in the ps
500 # command string because it creates yet another process which is of no interest to the caller.
Michael Walsh3b621fe2018-07-24 16:27:53 -0500501 temp = tempfile.NamedTemporaryFile()
502 temp_file_path = temp.name
503 gc.shell_cmd(ps_cmd_buf + " > " + temp_file_path,
504 print_output=print_output)
505 # Sample contents of the temporary file:
506 # 30703 sleep 2
Michael Walsh410b1782019-10-22 15:56:18 -0500507 # 30795 /bin/bash -c ps --no-headers --ppid 30672 -o pid,args > /tmp/tmpqqorWY
508 # Use egrep to exclude the "ps" process itself from the results collected with the prior shell_cmd
509 # invocation. Only the other children are of interest to the caller. Use cut on the grep results to
510 # obtain only the pid column.
Michael Walsh3b621fe2018-07-24 16:27:53 -0500511 rc, output = \
512 gc.shell_cmd("egrep -v '" + re.escape(ps_cmd_buf) + "' "
513 + temp_file_path + " | cut -c1-5",
514 print_output=print_output)
Michael Walsh410b1782019-10-22 15:56:18 -0500515 # Split the output buffer by line into a list. Strip each element of extra spaces and convert each
516 # element to an integer.
Michael Walsh3b621fe2018-07-24 16:27:53 -0500517 return map(int, map(str.strip, filter(None, output.split("\n"))))
Michael Walsh6aa69802018-09-21 16:38:34 -0500518
519
520def json_loads_multiple(buffer):
521 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500522 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 -0500523
524 The buffer is expected to contain one or more JSON objects.
525
526 Description of argument(s):
527 buffer A string containing several JSON objects.
528 """
529
Michael Walsh410b1782019-10-22 15:56:18 -0500530 # Any line consisting of just "}", which indicates the end of an object, should have a comma appended.
Michael Walsh6aa69802018-09-21 16:38:34 -0500531 regex = "([\\r\\n])[\\}]([\\r\\n])"
532 buffer = re.sub(regex, "\\1},\\2", buffer, 1)
Michael Walsh410b1782019-10-22 15:56:18 -0500533 # Remove the comma from after the final object and place the whole buffer inside square brackets.
Michael Walsh6aa69802018-09-21 16:38:34 -0500534 buffer = "[" + re.sub(",([\r\n])$", "\\1}", buffer, 1) + "]"
535 if gp.robot_env:
536 return json.loads(buffer, object_pairs_hook=DotDict)
537 else:
538 return json.loads(buffer, object_pairs_hook=collections.OrderedDict)
Michael Walshc9cb9722018-10-01 17:56:20 -0500539
540
541def file_date_time_stamp():
542 r"""
543 Return a date/time stamp in the following format: yymmdd.HHMMSS
544
Michael Walsh410b1782019-10-22 15:56:18 -0500545 This value is suitable for including in file names. Example file1.181001.171716.status
Michael Walshc9cb9722018-10-01 17:56:20 -0500546 """
547
548 return time.strftime("%y%m%d.%H%M%S", time.localtime(time.time()))
Michael Walshf5293d22019-02-01 14:23:02 -0600549
550
551def get_function_stack():
552 r"""
553 Return a list of all the function names currently in the call stack.
554
Michael Walsh410b1782019-10-22 15:56:18 -0500555 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 -0600556 """
557
558 return [str(stack_frame[3]) for stack_frame in inspect.stack()]
Michael Walsh2ce1dba2019-02-05 19:29:28 +0000559
560
561def username():
562 r"""
563 Return the username for the current process.
564 """
565
566 username = os.environ.get("USER", "")
567 if username != "":
568 return username
569 user_num = str(os.geteuid())
570 try:
571 username = os.getlogin()
572 except OSError:
573 if user_num == "0":
574 username = "root"
575 else:
576 username = "?"
577
578 return username
Michael Walsh97081e82019-08-20 17:07:46 +0000579
580
581def version_tuple(version):
582 r"""
583 Convert the version string to a tuple and return it.
584
585 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500586 version A version string whose format is "n[.n]" (e.g. "3.6.3", "3", etc.).
Michael Walsh97081e82019-08-20 17:07:46 +0000587 """
588
589 return tuple(map(int, (version.split("."))))
590
591
Michael Walsh410b1782019-10-22 15:56:18 -0500592# Note: Stripping out any revision code data (e.g. "3.6.3rc1" will become "3.6.3").
Michael Walsh97081e82019-08-20 17:07:46 +0000593python_version = \
594 version_tuple(re.sub("rc[^ ]+", "", sys.version).split(" ")[0])
595ordered_dict_version = version_tuple("3.6")