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:
6 # http://www.apache.org/licenses/LICENSE-2.0
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.
14 """Utility function for handling options without doubled or trailing spaces."""
18 """Class serving as a builder for option strings.
20 Motivation: Both manual concatenation and .join() methods
21 are prone to leaving superfluous spaces if some parts of options
22 are optional (missing, empty).
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.
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).
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.
39 def __init__(self, parts=tuple(), prefix=u""):
40 """Create instance with listed strings as parts to use.
42 Prefix will be converted to string and stripped.
43 The typical (nonempty) prefix values are "-" and "--".
45 TODO: Support users calling with parts being a string?
47 :param parts: List of stringifiable objects to become parts.
48 :param prefix: Substring to prepend to every parameter (not value).
49 :type parts: Iterable of object
52 self.parts = [str(part) for part in parts]
53 self.prefix = str(prefix).strip() # Not worth to call change_prefix.
56 """Return string executable as Python constructor call.
58 :returns: Executable constructor call as string.
61 return f"OptionString(parts={self.parts!r},prefix={self.prefix!r})"
63 # TODO: Would we ever need a copy() method?
64 # Currently, superstring "master" is mutable but unique,
65 # substring "slave" can be used to extend, but does not need to be mutated.
67 def change_prefix(self, prefix):
68 """Change the prefix field from the initialized value.
70 Sometimes it is more convenient to change the prefix in the middle
71 of string construction.
72 Typical use is for constructing a command, where the first part
73 (executeble filename) does not have a dash, but the other parameters do.
74 You could put the first part into constructor argument,
75 but using .add and only then enabling prefix is horizontally shorter.
77 :param prefix: New prefix value, to be converted and tripped.
79 :returns: Self, to enable method chaining.
82 self.prefix = str(prefix).strip()
84 def extend(self, other):
85 """Extend self by contents of other option string.
87 :param other: Another instance to add to the end of self.
88 :type other: OptionString
89 :returns: Self, to enable method chaining.
92 self.parts.extend(other.parts)
95 def check_and_add(self, part, prefixed):
96 """Convert to string, strip, conditionally add prefixed if non-empty.
98 Value of None is converted to empty string.
99 Emptiness is tested before adding prefix.
101 This could be a protected method (name starting with underscore),
102 but then pylint does not understand add_equals and add_with_value
103 are allowed to call this on the temp instance.
104 TODO: Is there a way to make pylint understand?
106 :param part: Unchecked part to add to list of parts.
107 :param prefixed: Whether to add prefix when adding.
109 :type prefixed: object
110 :returns: The converted part without prefix, empty means not added.
113 part = u"" if part is None else str(part).strip()
115 prefixed_part = self.prefix + part if prefixed else part
116 self.parts.append(prefixed_part)
119 def add(self, parameter):
120 """Add parameter if nonempty to the list of parts.
122 Parameter object is converted to string and stripped.
123 If parameter converts to empty string, nothing is added.
124 Parameter is prefixed before adding.
126 :param parameter: Parameter object, usually a word starting with dash.
127 :type parameter: object
128 :returns: Self, to enable method chaining.
131 self.check_and_add(parameter, prefixed=True)
134 def add_if(self, parameter, condition):
135 """Add parameter if nonempty and condition is true to the list of parts.
137 If condition truth value is false, nothing is added.
138 Parameter object is converted to string and stripped.
139 If parameter converts to empty string, nothing is added.
140 Parameter is prefixed before adding.
142 :param parameter: Parameter object, usually a word starting with dash.
143 :param condition: Do not add if truth value of this is false.
144 :type parameter: object
145 :type condition: object
146 :returns: Self, to enable method chaining.
153 def add_with_value(self, parameter, value):
154 """Add parameter, if followed by a value to the list of parts.
156 Parameter and value are converted to string and stripped.
157 If parameter or value converts to empty string, nothing is added.
158 If added, parameter (but not value) is prefixed.
160 :param parameter: Parameter object, usually a word starting with dash.
161 :param value: Value object. Prefix is never added.
162 :type parameter: object
164 :returns: Self, to enable method chaining.
167 temp = OptionString(prefix=self.prefix)
168 if temp.check_and_add(parameter, prefixed=True):
169 if temp.check_and_add(value, prefixed=False):
173 def add_equals(self, parameter, value):
174 """Add parameter=value to the list of parts.
176 Parameter and value are converted to string and stripped.
177 If parameter or value converts to empty string, nothing is added.
178 If added, parameter (but not value) is prefixed.
180 :param parameter: Parameter object, usually a word starting with dash.
181 :param value: Value object. Prefix is never added.
182 :type parameter: object
184 :returns: Self, to enable method chaining.
187 temp = OptionString(prefix=self.prefix)
188 if temp.check_and_add(parameter, prefixed=True):
189 if temp.check_and_add(value, prefixed=False):
190 self.parts.append(u"=".join(temp.parts))
193 def add_with_value_if(self, parameter, value, condition):
194 """Add parameter and value if condition is true and nothing is empty.
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.
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 parameter: object
206 :type condition: object
207 :returns: Self, to enable method chaining.
211 self.add_with_value(parameter, value)
214 def add_equals_if(self, parameter, value, condition):
215 """Add parameter=value to the list of parts if condition is true.
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.
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 parameter: object
227 :type condition: object
228 :returns: Self, to enable method chaining.
232 self.add_equals(parameter, value)
235 def add_with_value_from_dict(self, parameter, key, mapping, default=u""):
236 """Add parameter with value from dict under key, or default.
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.
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
250 :type default: object
251 :returns: Self, to enable method chaining.
254 value = mapping.get(key, default)
255 return self.add_with_value(parameter, value)
257 def add_equals_from_dict(self, parameter, key, mapping, default=u""):
258 """Add parameter=value to options where value is from dict.
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.
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
272 :type default: object
273 :returns: Self, to enable method chaining.
276 value = mapping.get(key, default)
277 return self.add_equals(parameter, value)
279 def add_if_from_dict(self, parameter, key, mapping, default=u"False"):
280 """Add parameter based on if the condition in dict is true.
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.
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
295 :type default: object
296 :returns: Self, to enable method chaining.
299 condition = mapping.get(key, default)
300 return self.add_if(parameter, condition)
302 def add_with_value_if_from_dict(
303 self, parameter, value, key, mapping, default=u"False"):
304 """Add parameter and value based on condition in dict.
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.
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
321 :type default: object
322 :returns: Self, to enable method chaining.
325 condition = mapping.get(key, default)
326 return self.add_with_value_if(parameter, value, condition)
328 def add_equals_if_from_dict(
329 self, parameter, value, key, mapping, default=u"False"):
330 """Add parameter=value based on condition in dict.
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.
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
347 :type default: object
348 :returns: Self, to enable method chaining.
351 condition = mapping.get(key, default)
352 return self.add_equals_if(parameter, value, condition)
355 """Return space separated string of nonempty parts.
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.
361 :returns: Space separated string of options.
364 return u" ".join(self.parts)