Use PapiSocketProvider for most PAPI calls
[csit.git] / resources / libraries / python / OptionString.py
1 # Copyright (c) 2019 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """Utility function for handling options without doubled or trailing spaces."""
15
16
17 class OptionString(object):
18     """Class serving as a builder for option strings.
19
20     Motivation: Both manual contatenation and .join() methods
21     are prone to leaving superfluous spaces if some parts of options
22     are optional (missing, empty).
23
24     The scope of this class is more general than just command line options,
25     it can concatenate any string consisting of words that may be missing.
26     But options were the first usage, so method arguments are frequently
27     named "parameter" and "value".
28     To keep this generality, automated adding of dashes is optional,
29     and disabled by default.
30
31     Parts of the whole option string are kept as list items (string, stipped),
32     with prefix already added.
33     Empty strings are never added to the list (except by constructor).
34
35     The class offers many methods for adding, so that callers can pick
36     the best fitting one, without much logic near the call site.
37     """
38
39     def __init__(self, parts=tuple(), prefix=""):
40         """Create instance with listed strings as parts to use.
41
42         Prefix will be converted to string and stripped.
43         The typical (nonempty) prefix values are "-" and "--".
44
45         TODO: Support users calling with parts being a string?
46
47         :param parts: List of of stringifiable objects to become parts.
48         :param prefix: Subtring to prepend to every parameter (not value).
49         :type parts: Iterable of object
50         :type prefix: object
51         """
52         self.parts = [str(part) for part in parts]
53         self.prefix = str(prefix).strip()  # Not worth to call change_prefix.
54
55     def __repr__(self):
56         """Return string executable as Python constructor call.
57
58         :returns: Executable constructor call as string.
59         :rtype: str
60         """
61         return "OptionString(parts={parts!r},prefix={prefix!r})".format(
62             parts=self.parts, prefix=self.prefix)
63
64     # TODO: Would we ever need a copy() method?
65     # Currently, superstring "master" is mutable but unique,
66     # substring "slave" can be used to extend, but does not need to be mutated.
67
68     def change_prefix(self, prefix):
69         """Change the prefix field from the initialized value.
70
71         Sometimes it is more convenient to change the prefix in the middle
72         of string construction.
73         Typical use is for constructing a command, where the first part
74         (executeble filename) does not have a dash, but the other parameters do.
75         You could put the first part into constructor argument,
76         but using .add and only then enabling prefix is horizontally shorter.
77
78         :param prefix: New prefix value, to be converted and tripped.
79         :type prefix: object
80         :returns: Self, to enable method chaining.
81         :rtype: OptionString
82         """
83         self.prefix = str(prefix).strip()
84
85     def extend(self, other):
86         """Extend self by contents of other option string.
87
88         :param other: Another instance to add to the end of self.
89         :type other: OptionString
90         :returns: Self, to enable method chaining.
91         :rtype: OptionString
92         """
93         self.parts.extend(other.parts)
94         return self
95
96     def _check_and_add(self, part, prefixed):
97         """Convert to string, strip, conditionally add prefixed if non-empty.
98
99         Value of None is converted to empty string.
100         Emptiness is tested before adding prefix.
101
102         :param part: Unchecked part to add to list of parts.
103         :param prefixed: Whether to add prefix when adding.
104         :type part: object
105         :type prefixed: object
106         :returns: The converted part without prefix, empty means not added.
107         :rtype: str
108         """
109         part = "" if part is None else str(part).strip()
110         if part:
111             prefixed_part = self.prefix + part if prefixed else part
112             self.parts.append(prefixed_part)
113         return part
114
115     def add(self, parameter):
116         """Add parameter if nonempty to the list of parts.
117
118         Parameter object is converted to string and stripped.
119         If parameter converts to empty string, nothing is added.
120         Parameter is prefixed before adding.
121
122         :param parameter: Parameter object, usually a word starting with dash.
123         :type variable: object
124         :returns: Self, to enable method chaining.
125         :rtype: OptionString
126         """
127         self._check_and_add(parameter, prefixed=True)
128         return self
129
130     def add_if(self, parameter, condition):
131         """Add parameter if nonempty and condition is true to the list of parts.
132
133         If condition truth value is false, nothing is added.
134         Parameter object is converted to string and stripped.
135         If parameter converts to empty string, nothing is added.
136         Parameter is prefixed before adding.
137
138         :param parameter: Parameter object, usually a word starting with dash.
139         :param condition: Do not add if truth value of this is false.
140         :type variable: object
141         :type condition: object
142         :returns: Self, to enable method chaining.
143         :rtype: OptionString
144         """
145         if condition:
146             self.add(parameter)
147         return self
148
149     def add_with_value(self, parameter, value):
150         """Add parameter, if followed by a value to the list of parts.
151
152         Parameter and value are converted to string and stripped.
153         If parameter or value converts to empty string, nothing is added.
154         If added, parameter (but not value) is prefixed.
155
156         :param parameter: Parameter object, usually a word starting with dash.
157         :param value: Value object. Prefix is never added.
158         :type variable: object
159         :type value: object
160         :returns: Self, to enable method chaining.
161         :rtype: OptionString
162         """
163         temp = OptionString(prefix=self.prefix)
164         # TODO: Is pylint really that ignorant?
165         # How could it not understand temp is of type of this class?
166         # pylint: disable=protected-access
167         if temp._check_and_add(parameter, prefixed=True):
168             if temp._check_and_add(value, prefixed=False):
169                 self.extend(temp)
170         return self
171
172     def add_equals(self, parameter, value):
173         """Add parameter=value to the list of parts.
174
175         Parameter and value are converted to string and stripped.
176         If parameter or value converts to empty string, nothing is added.
177         If added, parameter (but not value) is prefixed.
178
179         :param parameter: Parameter object, usually a word starting with dash.
180         :param value: Value object. Prefix is never added.
181         :type variable: object
182         :type value: object
183         :returns: Self, to enable method chaining.
184         :rtype: OptionString
185         """
186         temp = OptionString(prefix=self.prefix)
187         # pylint: disable=protected-access
188         if temp._check_and_add(parameter, prefixed=True):
189             if temp._check_and_add(value, prefixed=False):
190                 self.parts.append("=".join(temp.parts))
191         return self
192
193     def add_with_value_if(self, parameter, value, condition):
194         """Add parameter and value if condition is true and nothing is empty.
195
196         If condition truth value is false, nothing is added.
197         Parameter and value are converted to string and stripped.
198         If parameter or value converts to empty string, nothing is added.
199         If added, parameter (but not value) is prefixed.
200
201         :param parameter: Parameter object, usually a word starting with dash.
202         :param value: Value object. Prefix is never added.
203         :param condition: Do not add if truth value of this is false.
204         :type variable: object
205         :type value: object
206         :type condition: object
207         :returns: Self, to enable method chaining.
208         :rtype: OptionString
209         """
210         if condition:
211             self.add_with_value(parameter, value)
212         return self
213
214     def add_equals_if(self, parameter, value, condition):
215         """Add parameter=value to the list of parts if condition is true.
216
217         If condition truth value is false, nothing is added.
218         Parameter and value are converted to string and stripped.
219         If parameter or value converts to empty string, nothing is added.
220         If added, parameter (but not value) is prefixed.
221
222         :param parameter: Parameter object, usually a word starting with dash.
223         :param value: Value object. Prefix is never added.
224         :param condition: Do not add if truth value of this is false.
225         :type variable: object
226         :type value: object
227         :type condition: object
228         :returns: Self, to enable method chaining.
229         :rtype: OptionString
230         """
231         if condition:
232             self.add_equals(parameter, value)
233         return self
234
235     def add_with_value_from_dict(self, parameter, key, mapping, default=""):
236         """Add parameter with value from dict under key, or default.
237
238         If key is missing, default is used as value.
239         Parameter and value are converted to string and stripped.
240         If parameter or value converts to empty string, nothing is added.
241         If added, parameter (but not value) is prefixed.
242
243         :param parameter: The parameter part to add with prefix.
244         :param key: The key to look the value for.
245         :param mapping: Mapping with keys and values to use.
246         :param default: The value to use if key is missing.
247         :type parameter: object
248         :type key: str
249         :type mapping: dict
250         :type default: object
251         :returns: Self, to enable method chaining.
252         :rtype: OptionString
253         """
254         value = mapping.get(key, default)
255         return self.add_with_value(parameter, value)
256
257     def add_equals_from_dict(self, parameter, key, mapping, default=""):
258         """Add parameter=value to options where value is from dict.
259
260         If key is missing, default is used as value.
261         Parameter and value are converted to string and stripped.
262         If parameter or value converts to empty string, nothing is added.
263         If added, parameter (but not value) is prefixed.
264
265         :param parameter: The parameter part to add with prefix.
266         :param key: The key to look the value for.
267         :param mapping: Mapping with keys and values to use.
268         :param default: The value to use if key is missing.
269         :type parameter: object
270         :type key: str
271         :type mapping: dict
272         :type default: object
273         :returns: Self, to enable method chaining.
274         :rtype: OptionString
275         """
276         value = mapping.get(key, default)
277         return self.add_equals(parameter, value)
278
279     def add_if_from_dict(self, parameter, key, mapping, default="False"):
280         """Add parameter based on if the condition in dict is true.
281
282         If key is missing, default is used as condition.
283         If condition truth value is false, nothing is added.
284         Parameter is converted to string and stripped.
285         If parameter converts to empty string, nothing is added.
286         Parameter is prefixed before adding.
287
288         :param parameter: The parameter part to add with prefix.
289         :param key: The key to look the value for.
290         :param mapping: Mapping with keys and values to use.
291         :param default: The value to use if key is missing.
292         :type parameter: object
293         :type key: str
294         :type mapping: dict
295         :type default: object
296         :returns: Self, to enable method chaining.
297         :rtype: OptionString
298         """
299         condition = mapping.get(key, default)
300         return self.add_if(parameter, condition)
301
302     def add_with_value_if_from_dict(
303             self, parameter, value, key, mapping, default="False"):
304         """Add parameter and value based on condition in dict.
305
306         If key is missing, default is used as condition.
307         If condition truth value is false, nothing is added.
308         Parameter and value are converted to string and stripped.
309         If parameter or value converts to empty string, nothing is added.
310         If added, parameter (but not value) is prefixed.
311
312         :param parameter: The parameter part to add with prefix.
313         :param value: Value object. Prefix is never added.
314         :param key: The key to look the value for.
315         :param mapping: Mapping with keys and values to use.
316         :param default: The value to use if key is missing.
317         :type parameter: object
318         :type value: object
319         :type key: str
320         :type mapping: dict
321         :type default: object
322         :returns: Self, to enable method chaining.
323         :rtype: OptionString
324         """
325         condition = mapping.get(key, default)
326         return self.add_with_value_if(parameter, value, condition)
327
328     def add_equals_if_from_dict(
329             self, parameter, value, key, mapping, default="False"):
330         """Add parameter=value based on condition in dict.
331
332         If key is missing, default is used as condition.
333         If condition truth value is false, nothing is added.
334         Parameter and value are converted to string and stripped.
335         If parameter or value converts to empty string, nothing is added.
336         If added, parameter (but not value) is prefixed.
337
338         :param parameter: The parameter part to add with prefix.
339         :param value: Value object. Prefix is never added.
340         :param key: The key to look the value for.
341         :param mapping: Mapping with keys and values to use.
342         :param default: The value to use if key is missing.
343         :type parameter: object
344         :type value: object
345         :type key: str
346         :type mapping: dict
347         :type default: object
348         :returns: Self, to enable method chaining.
349         :rtype: OptionString
350         """
351         condition = mapping.get(key, default)
352         return self.add_equals_if(parameter, value, condition)
353
354     def __str__(self):
355         """Return space separated string of nonempty parts.
356
357         The format is suitable to be pasted as (part of) command line.
358         Do not call str() prematurely just to get a substring, consider
359         converting the surrounding text manipulation to OptionString as well.
360
361         :returns: Space separated string of options.
362         :rtype: str
363         """
364         return " ".join(self.parts)