blob: d7a6373e5277b17d17de26d3b2d4cdc84946b0f5 [file] [log] [blame]
George Keishinge7e91712021-09-03 11:28:44 -05001#!/usr/bin/env python3
Michael Walshde791732016-09-06 14:25:24 -05002
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
Patrick Williams20f38712022-12-08 06:18:26 -06007import collections
8import errno
9import inspect
10import json
11import os
12import random
13import shutil
14
Michael Walshde791732016-09-06 14:25:24 -050015# sys and os are needed to get the program dir path and program name.
16import sys
Michael Walshc9cb9722018-10-01 17:56:20 -050017import time
Patrick Williams20f38712022-12-08 06:18:26 -060018
Michael Walshdece16c2018-08-07 15:01:05 -050019try:
20 import ConfigParser
21except ImportError:
22 import configparser
George Keishing3b7115a2018-08-02 10:48:17 -050023try:
Michael Walsh61f5e8f2018-08-03 11:16:00 -050024 import StringIO
George Keishing3b7115a2018-08-02 10:48:17 -050025except ImportError:
Michael Walshdece16c2018-08-07 15:01:05 -050026 import io
Patrick Williams20f38712022-12-08 06:18:26 -060027
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060028import re
Michael Walsh1c85bab2017-05-04 14:29:24 -050029import socket
Michael Walsh3b621fe2018-07-24 16:27:53 -050030import tempfile
Patrick Williams20f38712022-12-08 06:18:26 -060031
Michael Walsh3b621fe2018-07-24 16:27:53 -050032try:
33 import psutil
Patrick Williams20f38712022-12-08 06:18:26 -060034
Michael Walsh3b621fe2018-07-24 16:27:53 -050035 psutil_imported = True
36except ImportError:
37 psutil_imported = False
Michael Walshde791732016-09-06 14:25:24 -050038
George Keishinge635ddc2022-12-08 07:38:02 -060039import gen_cmd as gc
Patrick Williams20f38712022-12-08 06:18:26 -060040import gen_print as gp
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060041
Michael Walsh93a09f22017-11-13 15:34:46 -060042robot_env = gp.robot_env
43if robot_env:
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060044 from robot.libraries.BuiltIn import BuiltIn
Michael Walshdece16c2018-08-07 15:01:05 -050045 from robot.utils import DotDict
Michael Walsh7423c012016-10-04 10:27:21 -050046
Michael Walshde791732016-09-06 14:25:24 -050047
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060048def add_trailing_slash(dir_path):
Michael Walsh7db77942017-01-10 11:37:06 -060049 r"""
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060050 Add a trailing slash to the directory path if it doesn't already have one
51 and return it.
Michael Walsh7db77942017-01-10 11:37:06 -060052
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060053 Description of arguments:
54 dir_path A directory path.
Michael Walsh7db77942017-01-10 11:37:06 -060055 """
56
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060057 return os.path.normpath(dir_path) + os.path.sep
58
Michael Walsh0f2ea5f2017-02-20 15:55:00 -060059
Michael Walshc4d6bce2020-02-18 14:25:44 -060060def makedirs(path, mode=0o777, quiet=None):
61 r"""
62 Call os.makedirs with the caller's arguments.
63
64 This function offers 2 advantages over the base os.makedirs function:
65 1) It will not fail if the directory already exists.
66 2) It will print an "Issuing: os.makedirs" message.
67
68 Description of argument(s):
69 path The path containing the directories to be created.
70 mode The mode or permissions to be granted to the created directories.
71 quiet Indicates whether this function should run the print_issuing() function.
72 """
Patrick Williams20f38712022-12-08 06:18:26 -060073 quiet = int(dft(quiet, gp.get_stack_var("quiet", 0)))
Michael Walshc4d6bce2020-02-18 14:25:44 -060074 gp.qprint_issuing("os.makedirs('" + path + "', mode=" + oct(mode) + ")")
75 try:
76 os.makedirs(path, mode)
Michael Walsh1435b352020-02-27 15:22:55 -060077 except OSError:
Michael Walshc4d6bce2020-02-18 14:25:44 -060078 pass
79
80
Michael Walsh37cade82020-03-26 17:32:27 -050081def rmtree(path, ignore_errors=False, onerror=None, quiet=None):
82 r"""
83 Call shutil.rmtree with the caller's arguments.
84
85 This function offers this advantage over the base function:
86 - It will print an "Issuing: shutil.rmtree" message.
87
88 Description of argument(s):
89 (All parms are passed directly to shutil.rmtree. See its prolog for details)
90 quiet Indicates whether this function should run the print_issuing() function.
91 """
Patrick Williams20f38712022-12-08 06:18:26 -060092 quiet = int(dft(quiet, gp.get_stack_var("quiet", 0)))
Michael Walsh37cade82020-03-26 17:32:27 -050093 print_string = gp.sprint_executing(max_width=2000)
Patrick Williams20f38712022-12-08 06:18:26 -060094 print_string = re.sub(
95 r"Executing: ", "Issuing: shutil.", print_string.rstrip("\n")
96 )
Michael Walsh37cade82020-03-26 17:32:27 -050097 gp.qprintn(re.sub(r", quiet[ ]?=.*", ")", print_string))
98 shutil.rmtree(path, ignore_errors, onerror)
99
100
Michael Walshc4d6bce2020-02-18 14:25:44 -0600101def chdir(path, quiet=None):
102 r"""
103 Call os.chdir with the caller's arguments.
104
105 This function offers this advantage over the base os.chdir function:
106 - It will print an "Issuing: os.chdir" message.
107
108 Description of argument(s):
109 path The path of the directory to change to.
110 quiet Indicates whether this function should run the print_issuing() function.
111 """
Patrick Williams20f38712022-12-08 06:18:26 -0600112 quiet = int(dft(quiet, gp.get_stack_var("quiet", 0)))
Michael Walshc4d6bce2020-02-18 14:25:44 -0600113 gp.qprint_issuing("os.chdir('" + path + "')")
114 os.chdir(path)
115
116
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600117def which(file_path):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600118 r"""
119 Find the full path of an executable file and return it.
120
121 The PATH environment variable dictates the results of this function.
122
123 Description of arguments:
Michael Walsh410b1782019-10-22 15:56:18 -0500124 file_path The relative file path (e.g. "my_file" or "lib/my_file").
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600125 """
126
Patrick Williams20f38712022-12-08 06:18:26 -0600127 shell_rc, out_buf = gc.cmd_fnc_u(
128 "which " + file_path, quiet=1, print_output=0, show_err=0
129 )
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600130 if shell_rc != 0:
Patrick Williams20f38712022-12-08 06:18:26 -0600131 error_message = (
132 'Failed to find complete path for file "' + file_path + '".\n'
133 )
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500134 error_message += gp.sprint_var(shell_rc, gp.hexa())
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600135 error_message += out_buf
136 if robot_env:
137 BuiltIn().fail(gp.sprint_error(error_message))
138 else:
139 gp.print_error_report(error_message)
140 return False
141
142 file_path = out_buf.rstrip("\n")
143
144 return file_path
145
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600146
Patrick Williams20f38712022-12-08 06:18:26 -0600147def add_path(new_path, path, position=0):
Michael Walshf7400f32018-09-26 17:13:43 -0500148 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500149 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 -0500150
151 Example:
152 If PATH has a value of "/bin/user:/lib/user". The following code:
153
154 PATH = add_path("/tmp/new_path", PATH)
155
156 will change PATH to "/tmp/new_path:/bin/user:/lib/user".
157
158 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500159 new_path The path to be added. This function will strip the trailing slash.
160 path The path value to which the new_path should be added.
161 position The position in path where the new_path should be added. 0 means it
162 should be added to the beginning, 1 means add it as the 2nd item, etc.
163 sys.maxsize means it should be added to the end.
Michael Walshf7400f32018-09-26 17:13:43 -0500164 """
165
166 path_list = list(filter(None, path.split(":")))
167 new_path = new_path.rstrip("/")
168 if new_path not in path_list:
169 path_list.insert(int(position), new_path)
170 return ":".join(path_list)
171
172
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600173def dft(value, default):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600174 r"""
175 Return default if value is None. Otherwise, return value.
176
177 This is really just shorthand as shown below.
178
179 dft(value, default)
180
181 vs
182
183 default if value is None else value
184
185 Description of arguments:
186 value The value to be returned.
Michael Walsh410b1782019-10-22 15:56:18 -0500187 default The default value to return if value is None.
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600188 """
189
190 return default if value is None else value
191
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600192
Patrick Williams20f38712022-12-08 06:18:26 -0600193def get_mod_global(var_name, default=None, mod_name="__main__"):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600194 r"""
195 Get module global variable value and return it.
196
197 If we are running in a robot environment, the behavior will default to
198 calling get_variable_value.
199
200 Description of arguments:
Michael Walsh410b1782019-10-22 15:56:18 -0500201 var_name The name of the variable whose value is sought.
202 default The value to return if the global does not exist.
203 mod_name The name of the module containing the global variable.
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600204 """
205
206 if robot_env:
207 return BuiltIn().get_variable_value("${" + var_name + "}", default)
208
209 try:
210 module = sys.modules[mod_name]
211 except KeyError:
Patrick Williams20f38712022-12-08 06:18:26 -0600212 gp.print_error_report(
213 "Programmer error - The mod_name passed to"
214 + " this function is invalid:\n"
215 + gp.sprint_var(mod_name)
216 )
217 raise ValueError("Programmer error.")
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600218
219 if default is None:
220 return getattr(module, var_name)
221 else:
222 return getattr(module, var_name, default)
223
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600224
Patrick Williams20f38712022-12-08 06:18:26 -0600225def global_default(var_value, default=0):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600226 r"""
227 If var_value is not None, return it. Otherwise, return the global
228 variable of the same name, if it exists. If not, return default.
229
230 This is meant for use by functions needing help assigning dynamic default
231 values to their parms. Example:
232
233 def func1(parm1=None):
234
235 parm1 = global_default(parm1, 0)
236
237 Description of arguments:
238 var_value The value being evaluated.
Michael Walsh410b1782019-10-22 15:56:18 -0500239 default The value to be returned if var_value is None AND the global variable of
240 the same name does not exist.
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600241 """
242
243 var_name = gp.get_arg_name(0, 1, stack_frame_ix=2)
244
245 return dft(var_value, get_mod_global(var_name, 0))
Michael Walsh7db77942017-01-10 11:37:06 -0600246
Michael Walsh7db77942017-01-10 11:37:06 -0600247
Patrick Williams20f38712022-12-08 06:18:26 -0600248def set_mod_global(var_value, mod_name="__main__", var_name=None):
Michael Walsh7db77942017-01-10 11:37:06 -0600249 r"""
250 Set a global variable for a given module.
251
252 Description of arguments:
253 var_value The value to set in the variable.
Michael Walsh410b1782019-10-22 15:56:18 -0500254 mod_name The name of the module whose variable is to be set.
255 var_name The name of the variable to set. This defaults to the name of the
256 variable used for var_value when calling this function.
Michael Walsh7db77942017-01-10 11:37:06 -0600257 """
258
259 try:
260 module = sys.modules[mod_name]
261 except KeyError:
Patrick Williams20f38712022-12-08 06:18:26 -0600262 gp.print_error_report(
263 "Programmer error - The mod_name passed to"
264 + " this function is invalid:\n"
265 + gp.sprint_var(mod_name)
266 )
267 raise ValueError("Programmer error.")
Michael Walsh7db77942017-01-10 11:37:06 -0600268
269 if var_name is None:
270 var_name = gp.get_arg_name(None, 1, 2)
271
272 setattr(module, var_name, var_value)
273
Michael Walsh7db77942017-01-10 11:37:06 -0600274
Michael Walshde791732016-09-06 14:25:24 -0500275def my_parm_file(prop_file_path):
Michael Walshde791732016-09-06 14:25:24 -0500276 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500277 Read a properties file, put the keys/values into a dictionary and return the dictionary.
Michael Walshde791732016-09-06 14:25:24 -0500278
279 The properties file must have the following format:
280 var_name<= or :>var_value
Michael Walsh410b1782019-10-22 15:56:18 -0500281 Comment lines (those beginning with a "#") and blank lines are allowed and will be ignored. Leading and
282 trailing single or double quotes will be stripped from the value. E.g.
Michael Walshde791732016-09-06 14:25:24 -0500283 var1="This one"
284 Quotes are stripped so the resulting value for var1 is:
285 This one
286
287 Description of arguments:
Michael Walsh410b1782019-10-22 15:56:18 -0500288 prop_file_path The caller should pass the path to the properties file.
Michael Walshde791732016-09-06 14:25:24 -0500289 """
290
Michael Walsh410b1782019-10-22 15:56:18 -0500291 # ConfigParser expects at least one section header in the file (or you get
292 # ConfigParser.MissingSectionHeaderError). Properties files don't need those so I'll write a dummy
293 # section header.
Michael Walshde791732016-09-06 14:25:24 -0500294
Michael Walshdece16c2018-08-07 15:01:05 -0500295 try:
296 string_file = StringIO.StringIO()
297 except NameError:
298 string_file = io.StringIO()
299
Michael Walshde791732016-09-06 14:25:24 -0500300 # Write the dummy section header to the string file.
Patrick Williams20f38712022-12-08 06:18:26 -0600301 string_file.write("[dummysection]\n")
Michael Walshde791732016-09-06 14:25:24 -0500302 # Write the entire contents of the properties file to the string file.
303 string_file.write(open(prop_file_path).read())
304 # Rewind the string file.
305 string_file.seek(0, os.SEEK_SET)
306
307 # Create the ConfigParser object.
Michael Walshdece16c2018-08-07 15:01:05 -0500308 try:
309 config_parser = ConfigParser.ConfigParser()
310 except NameError:
George Keishing36efbc02018-12-12 10:18:23 -0600311 config_parser = configparser.ConfigParser(strict=False)
Michael Walshde791732016-09-06 14:25:24 -0500312 # Make the property names case-sensitive.
313 config_parser.optionxform = str
314 # Read the properties from the string file.
315 config_parser.readfp(string_file)
316 # Return the properties as a dictionary.
Michael Walshdece16c2018-08-07 15:01:05 -0500317 if robot_env:
Patrick Williams20f38712022-12-08 06:18:26 -0600318 return DotDict(config_parser.items("dummysection"))
Michael Walshdece16c2018-08-07 15:01:05 -0500319 else:
Patrick Williams20f38712022-12-08 06:18:26 -0600320 return collections.OrderedDict(config_parser.items("dummysection"))
Michael Walshde791732016-09-06 14:25:24 -0500321
Michael Walsh7423c012016-10-04 10:27:21 -0500322
Patrick Williams20f38712022-12-08 06:18:26 -0600323def file_to_list(file_path, newlines=0, comments=1, trim=0):
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600324 r"""
325 Return the contents of a file as a list. Each element of the resulting
326 list is one line from the file.
327
328 Description of arguments:
Michael Walsh410b1782019-10-22 15:56:18 -0500329 file_path The path to the file (relative or absolute).
330 newlines Include newlines from the file in the results.
331 comments Include comment lines and blank lines in the results. Comment lines are
332 any that begin with 0 or more spaces followed by the pound sign ("#").
333 trim Trim white space from the beginning and end of each line.
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600334 """
335
336 lines = []
337 file = open(file_path)
338 for line in file:
339 if not comments:
340 if re.match(r"[ ]*#|^$", line):
341 continue
342 if not newlines:
343 line = line.rstrip("\n")
344 if trim:
345 line = line.strip()
346 lines.append(line)
Michael Walsh1383f352018-09-27 16:25:54 -0500347 file.close()
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600348
349 return lines
350
Michael Walsh0f2ea5f2017-02-20 15:55:00 -0600351
Michael Walsh1383f352018-09-27 16:25:54 -0500352def file_to_str(*args, **kwargs):
353 r"""
354 Return the contents of a file as a string.
355
356 Description of arguments:
357 See file_to_list defined above for description of arguments.
358 """
359
Patrick Williams20f38712022-12-08 06:18:26 -0600360 return "\n".join(file_to_list(*args, **kwargs))
Michael Walsh1383f352018-09-27 16:25:54 -0500361
362
Michael Walsh111a8312019-12-05 16:48:06 -0600363def append_file(file_path, buffer):
364 r"""
365 Append the data in buffer to the file named in file_path.
366
367 Description of argument(s):
368 file_path The path to a file (e.g. "/tmp/root/file1").
369 buffer The buffer of data to be written to the file (e.g. "this and that").
370 """
371
372 with open(file_path, "a") as file:
373 file.write(buffer)
374
375
Michael Walsh7423c012016-10-04 10:27:21 -0500376def return_path_list():
Michael Walsh7423c012016-10-04 10:27:21 -0500377 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500378 This function will split the PATH environment variable into a PATH_LIST and return it. Each element in
379 the list will be normalized and have a trailing slash added.
Michael Walsh7423c012016-10-04 10:27:21 -0500380 """
381
Patrick Williams20f38712022-12-08 06:18:26 -0600382 PATH_LIST = os.environ["PATH"].split(":")
Michael Walsh7423c012016-10-04 10:27:21 -0500383 PATH_LIST = [os.path.normpath(path) + os.sep for path in PATH_LIST]
384
385 return PATH_LIST
386
Michael Walsh7db77942017-01-10 11:37:06 -0600387
Michael Walsh9fac55c2017-09-29 16:53:56 -0500388def escape_bash_quotes(buffer):
Michael Walsh9fac55c2017-09-29 16:53:56 -0500389 r"""
390 Escape quotes in string and return it.
391
392 The escape style implemented will be for use on the bash command line.
393
394 Example:
395 That's all.
396
397 Result:
398 That'\''s all.
399
400 The result may then be single quoted on a bash command. Example:
401
402 echo 'That'\''s all.'
403
404 Description of argument(s):
405 buffer The string whose quotes are to be escaped.
406 """
407
Patrick Williams20f38712022-12-08 06:18:26 -0600408 return re.sub("'", "'\\''", buffer)
Michael Walsh9fac55c2017-09-29 16:53:56 -0500409
410
Michael Walsh7db77942017-01-10 11:37:06 -0600411def quote_bash_parm(parm):
Michael Walsh7db77942017-01-10 11:37:06 -0600412 r"""
413 Return the bash command line parm with single quotes if they are needed.
414
415 Description of arguments:
416 parm The string to be quoted.
417 """
418
Michael Walsh410b1782019-10-22 15:56:18 -0500419 # If any of these characters are found in the parm string, then the string should be quoted. This list
420 # is by no means complete and should be expanded as needed by the developer of this function.
Michael Walsh9fc17c32019-01-21 14:49:10 -0600421 # Spaces
422 # Single or double quotes.
423 # Bash variables (therefore, any string with a "$" may need quoting).
424 # Glob characters: *, ?, []
425 # Extended Glob characters: +, @, !
426 # Bash brace expansion: {}
427 # Tilde expansion: ~
428 # Piped commands: |
429 # Bash re-direction: >, <
Patrick Williams20f38712022-12-08 06:18:26 -0600430 bash_special_chars = set(" '\"$*?[]+@!{}~|><")
Michael Walsh7db77942017-01-10 11:37:06 -0600431
432 if any((char in bash_special_chars) for char in parm):
Michael Walsh9fc17c32019-01-21 14:49:10 -0600433 return "'" + escape_bash_quotes(parm) + "'"
434
Patrick Williams20f38712022-12-08 06:18:26 -0600435 if parm == "":
Michael Walsh9fc17c32019-01-21 14:49:10 -0600436 parm = "''"
Michael Walsh7db77942017-01-10 11:37:06 -0600437
438 return parm
439
Michael Walsh1c85bab2017-05-04 14:29:24 -0500440
Patrick Williams20f38712022-12-08 06:18:26 -0600441def get_host_name_ip(host=None, short_name=0):
Michael Walsh1c85bab2017-05-04 14:29:24 -0500442 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500443 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 -0500444
445 Description of argument(s):
Michael Walshd1b6c702017-05-30 17:54:30 -0500446 host The host name or IP address to be obtained.
Michael Walsh410b1782019-10-22 15:56:18 -0500447 short_name Include the short host name in the returned tuple, i.e. return host, ip
448 and short_host.
Michael Walsh1c85bab2017-05-04 14:29:24 -0500449 """
450
Michael Walsh74427232018-08-31 12:54:24 -0500451 host = dft(host, socket.gethostname())
Michael Walshf74b3e42018-01-10 11:11:54 -0600452 host_name = socket.getfqdn(host)
Michael Walshd1b6c702017-05-30 17:54:30 -0500453 try:
454 host_ip = socket.gethostbyname(host)
455 except socket.gaierror as my_gaierror:
Patrick Williams20f38712022-12-08 06:18:26 -0600456 message = (
457 "Unable to obtain the host name for the following host:"
458 + "\n"
459 + gp.sprint_var(host)
460 )
Michael Walshd1b6c702017-05-30 17:54:30 -0500461 gp.print_error_report(message)
462 raise my_gaierror
Michael Walsh1c85bab2017-05-04 14:29:24 -0500463
Michael Walshf74b3e42018-01-10 11:11:54 -0600464 if short_name:
465 host_short_name = host_name.split(".")[0]
466 return host_name, host_ip, host_short_name
467 else:
468 return host_name, host_ip
Michael Walsh1c85bab2017-05-04 14:29:24 -0500469
Michael Walsheaa16852017-09-19 16:30:43 -0500470
471def pid_active(pid):
Michael Walsheaa16852017-09-19 16:30:43 -0500472 r"""
473 Return true if pid represents an active pid and false otherwise.
474
475 Description of argument(s):
476 pid The pid whose status is being sought.
477 """
478
479 try:
480 os.kill(int(pid), 0)
481 except OSError as err:
482 if err.errno == errno.ESRCH:
483 # ESRCH == No such process
484 return False
485 elif err.errno == errno.EPERM:
486 # EPERM clearly means there's a process to deny access to
487 return True
488 else:
489 # According to "man 2 kill" possible error values are
490 # (EINVAL, EPERM, ESRCH)
491 raise
492
493 return True
Michael Walsh112c3592018-06-01 14:15:58 -0500494
495
Patrick Williams20f38712022-12-08 06:18:26 -0600496def to_signed(number, bit_width=None):
Michael Walsh112c3592018-06-01 14:15:58 -0500497 r"""
498 Convert number to a signed number and return the result.
499
500 Examples:
501
502 With the following code:
503
504 var1 = 0xfffffffffffffff1
505 print_var(var1)
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500506 print_var(var1, hexa())
Michael Walsh112c3592018-06-01 14:15:58 -0500507 var1 = to_signed(var1)
508 print_var(var1)
Michael Walsh0d5f96a2019-05-20 10:09:57 -0500509 print_var(var1, hexa())
Michael Walsh112c3592018-06-01 14:15:58 -0500510
511 The following is written to stdout:
512 var1: 18446744073709551601
513 var1: 0x00000000fffffffffffffff1
514 var1: -15
515 var1: 0xfffffffffffffff1
516
Michael Walsh410b1782019-10-22 15:56:18 -0500517 The same code but with var1 set to 0x000000000000007f produces the following:
Michael Walsh112c3592018-06-01 14:15:58 -0500518 var1: 127
519 var1: 0x000000000000007f
520 var1: 127
521 var1: 0x000000000000007f
522
523 Description of argument(s):
524 number The number to be converted.
Michael Walsh410b1782019-10-22 15:56:18 -0500525 bit_width The number of bits that defines a complete hex value. Typically, this
526 would be a multiple of 32.
Michael Walsh112c3592018-06-01 14:15:58 -0500527 """
528
Michael Walshdece16c2018-08-07 15:01:05 -0500529 if bit_width is None:
530 try:
531 bit_width = gp.bit_length(long(sys.maxsize)) + 1
532 except NameError:
533 bit_width = gp.bit_length(int(sys.maxsize)) + 1
534
Michael Walsh112c3592018-06-01 14:15:58 -0500535 if number < 0:
536 return number
Patrick Williams20f38712022-12-08 06:18:26 -0600537 neg_bit_mask = 2 ** (bit_width - 1)
Michael Walsh112c3592018-06-01 14:15:58 -0500538 if number & neg_bit_mask:
539 return ((2**bit_width) - number) * -1
540 else:
541 return number
Michael Walsh3b621fe2018-07-24 16:27:53 -0500542
543
544def get_child_pids(quiet=1):
Michael Walsh3b621fe2018-07-24 16:27:53 -0500545 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500546 Get and return a list of pids representing all first-generation processes that are the children of the
547 current process.
Michael Walsh3b621fe2018-07-24 16:27:53 -0500548
549 Example:
550
551 children = get_child_pids()
552 print_var(children)
553
554 Output:
555 children:
556 children[0]: 9123
557
558 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500559 quiet Display output to stdout detailing how this child pids are obtained.
Michael Walsh3b621fe2018-07-24 16:27:53 -0500560 """
561
562 if psutil_imported:
563 # If "import psutil" worked, find child pids using psutil.
564 current_process = psutil.Process()
565 return [x.pid for x in current_process.children(recursive=False)]
566 else:
567 # Otherwise, find child pids using shell commands.
568 print_output = not quiet
569
Patrick Williams20f38712022-12-08 06:18:26 -0600570 ps_cmd_buf = (
571 "ps --no-headers --ppid " + str(os.getpid()) + " -o pid,args"
572 )
Michael Walsh410b1782019-10-22 15:56:18 -0500573 # Route the output of ps to a temporary file for later grepping. Avoid using " | grep" in the ps
574 # command string because it creates yet another process which is of no interest to the caller.
Michael Walsh3b621fe2018-07-24 16:27:53 -0500575 temp = tempfile.NamedTemporaryFile()
576 temp_file_path = temp.name
Patrick Williams20f38712022-12-08 06:18:26 -0600577 gc.shell_cmd(
578 ps_cmd_buf + " > " + temp_file_path, print_output=print_output
579 )
Michael Walsh3b621fe2018-07-24 16:27:53 -0500580 # Sample contents of the temporary file:
581 # 30703 sleep 2
Michael Walsh410b1782019-10-22 15:56:18 -0500582 # 30795 /bin/bash -c ps --no-headers --ppid 30672 -o pid,args > /tmp/tmpqqorWY
583 # Use egrep to exclude the "ps" process itself from the results collected with the prior shell_cmd
584 # invocation. Only the other children are of interest to the caller. Use cut on the grep results to
585 # obtain only the pid column.
Patrick Williams20f38712022-12-08 06:18:26 -0600586 rc, output = gc.shell_cmd(
587 "egrep -v '"
588 + re.escape(ps_cmd_buf)
589 + "' "
590 + temp_file_path
591 + " | cut -c1-5",
592 print_output=print_output,
593 )
Michael Walsh410b1782019-10-22 15:56:18 -0500594 # Split the output buffer by line into a list. Strip each element of extra spaces and convert each
595 # element to an integer.
Michael Walsh3b621fe2018-07-24 16:27:53 -0500596 return map(int, map(str.strip, filter(None, output.split("\n"))))
Michael Walsh6aa69802018-09-21 16:38:34 -0500597
598
599def json_loads_multiple(buffer):
600 r"""
Michael Walsh410b1782019-10-22 15:56:18 -0500601 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 -0500602
603 The buffer is expected to contain one or more JSON objects.
604
605 Description of argument(s):
606 buffer A string containing several JSON objects.
607 """
608
Michael Walsh410b1782019-10-22 15:56:18 -0500609 # Any line consisting of just "}", which indicates the end of an object, should have a comma appended.
Michael Walsh6aa69802018-09-21 16:38:34 -0500610 regex = "([\\r\\n])[\\}]([\\r\\n])"
611 buffer = re.sub(regex, "\\1},\\2", buffer, 1)
Michael Walsh410b1782019-10-22 15:56:18 -0500612 # Remove the comma from after the final object and place the whole buffer inside square brackets.
Michael Walsh6aa69802018-09-21 16:38:34 -0500613 buffer = "[" + re.sub(",([\r\n])$", "\\1}", buffer, 1) + "]"
614 if gp.robot_env:
615 return json.loads(buffer, object_pairs_hook=DotDict)
616 else:
617 return json.loads(buffer, object_pairs_hook=collections.OrderedDict)
Michael Walshc9cb9722018-10-01 17:56:20 -0500618
619
620def file_date_time_stamp():
621 r"""
622 Return a date/time stamp in the following format: yymmdd.HHMMSS
623
Michael Walsh410b1782019-10-22 15:56:18 -0500624 This value is suitable for including in file names. Example file1.181001.171716.status
Michael Walshc9cb9722018-10-01 17:56:20 -0500625 """
626
627 return time.strftime("%y%m%d.%H%M%S", time.localtime(time.time()))
Michael Walshf5293d22019-02-01 14:23:02 -0600628
629
630def get_function_stack():
631 r"""
632 Return a list of all the function names currently in the call stack.
633
Michael Walsh410b1782019-10-22 15:56:18 -0500634 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 -0600635 """
636
637 return [str(stack_frame[3]) for stack_frame in inspect.stack()]
Michael Walsh2ce1dba2019-02-05 19:29:28 +0000638
639
640def username():
641 r"""
642 Return the username for the current process.
643 """
644
645 username = os.environ.get("USER", "")
646 if username != "":
647 return username
648 user_num = str(os.geteuid())
649 try:
650 username = os.getlogin()
651 except OSError:
652 if user_num == "0":
653 username = "root"
654 else:
655 username = "?"
656
657 return username
Michael Walsh97081e82019-08-20 17:07:46 +0000658
659
660def version_tuple(version):
661 r"""
662 Convert the version string to a tuple and return it.
663
664 Description of argument(s):
Michael Walsh410b1782019-10-22 15:56:18 -0500665 version A version string whose format is "n[.n]" (e.g. "3.6.3", "3", etc.).
Michael Walsh97081e82019-08-20 17:07:46 +0000666 """
667
668 return tuple(map(int, (version.split("."))))
669
670
Michael Walsh87353e32019-10-23 12:50:43 -0500671def get_python_version():
672 r"""
673 Get and return the python version.
674 """
675
676 sys_version = sys.version
677 # Strip out any revision code data (e.g. "3.6.3rc1" will become "3.6.3").
678 sys_version = re.sub("rc[^ ]+", "", sys_version).split(" ")[0]
679 # Remove any non-numerics, etc. (e.g. "2.7.15+" becomes ""2.7.15").
680 return re.sub("[^0-9\\.]", "", sys_version)
681
682
Patrick Williams20f38712022-12-08 06:18:26 -0600683python_version = version_tuple(get_python_version())
Michael Walsh97081e82019-08-20 17:07:46 +0000684ordered_dict_version = version_tuple("3.6")
Michael Walsh111a8312019-12-05 16:48:06 -0600685
686
687def create_temp_file_path(delim=":", suffix=""):
688 r"""
689 Create a temporary file path and return it.
690
691 This function is appropriate for users who with to create a temporary file and:
692 1) Have control over when and whether the file is deleted.
693 2) Have the name of the file indicate information such as program name, function name, line, pid, etc.
694 This can be an aid in debugging, cleanup, etc.
695
696 The dir path portion of the file path will be /tmp/<username>/. This function will create this directory
697 if it doesn't already exist.
698
699 This function will NOT create the file. The file will NOT automatically get deleted. It is the
700 responsibility of the caller to dispose of it.
701
702 Example:
703
704 pgm123.py is run by user 'joe'. It calls func1 which contains this code:
705
706 temp_file_path = create_temp_file_path(suffix='suffix1')
707 print_var(temp_file_path)
708
709 Output:
710
711 temp_file_path: /tmp/joe/pgm123.py:func1:line_55:pid_8199:831848:suffix1
712
713 Description of argument(s):
714 delim A delimiter to be used to separate the sub-components of the file name.
715 suffix A suffix to include as the last sub-component of the file name.
716 """
717
718 temp_dir_path = "/tmp/" + username() + "/"
719 try:
720 os.mkdir(temp_dir_path)
721 except FileExistsError:
722 pass
723
724 callers_stack_frame = inspect.stack()[1]
Patrick Williams20f38712022-12-08 06:18:26 -0600725 file_name_elements = [
726 gp.pgm_name,
727 callers_stack_frame.function,
728 "line_" + str(callers_stack_frame.lineno),
729 "pid_" + str(os.getpid()),
730 str(random.randint(0, 1000000)),
731 suffix,
732 ]
Michael Walsh111a8312019-12-05 16:48:06 -0600733 temp_file_name = delim.join(file_name_elements)
734
735 temp_file_path = temp_dir_path + temp_file_name
736
737 return temp_file_path
Michael Walsh05a7a6f2020-02-12 15:02:53 -0600738
739
740def pause(message="Hit enter to continue..."):
741 r"""
742 Print the message, with time stamp, and pause until the user hits enter.
743
744 Description of argument(s):
745 message The message to be printed to stdout.
746 """
747 gp.print_time(message)
748 try:
749 input()
750 except SyntaxError:
751 pass
752
753 return