blob: d7894bc5ad52d689635fb53cbcc421dfb0e5db55 [file] [log] [blame]
Srikanth Vavilapalli1725e492014-12-01 17:50:52 -08001#
2# Copyright (c) 2011,2012,2013 Big Switch Networks, Inc.
3#
4# Licensed under the Eclipse Public License, Version 1.0 (the
5# "License"); you may not use this file except in compliance with the
6# License. You may obtain a copy of the License at
7#
8# http://www.eclipse.org/legal/epl-v10.html
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13# implied. See the License for the specific language governing
14# permissions and limitations under the License.
15#
16
17#
18# UTIlity Functions
19#
20
21import re
22
23
24#
25# --------------------------------------------------------------------------------
26
27def is_power_of_two(n):
28 """
29 Return true if the integer parameter is a power of two
30 """
31 if (n & (n - 1)) == 0:
32 return True
33 return False
34
35#
36# --------------------------------------------------------------------------------
37
38def unique_list_from_list(dup_list):
39 """
40 Return a new list from the old, where the the new has no repeated items.
41 (the items are intended to be strings)
42 """
43 return dict([[x, None] for x in dup_list]).keys()
44
45#
46# --------------------------------------------------------------------------------
47
48def full_word_from_choices(word, all_choices):
49 """
50 given a single word, which could be a prefix of a word in
51 a list (which is the second parameter), return a single
52 word which matches or None, when non or more than one match.
53 """
54 choices = [x for x in all_choices if x.startswith(word)]
55 if len(choices) == 1:
56 return choices[0]
57 if word in all_choices: # also allow an exact match
58 return word
59 return None
60
61
62#
63# --------------------------------------------------------------------------------
64
65def try_int(string):
66 """
67 Return an interger if possible, otherwise a string
68 """
69 try:
70 str_int = int(string)
71 return str_int
72 except:
73 pass
74 return string
75
76#
77# --------------------------------------------------------------------------------
78
79def mask_to_cidr(mask):
80 """
81 Given a mask used in AC's, for eample 0.0.0.255, return the cidr /<n>
82 value associated with that mask. The mask must be a power of two for
83 the cidr value to be displayed
84 """
85 if not is_power_of_two(mask + 1):
86 return 0
87 mask += 1
88 cidr = 33
89 while mask:
90 cidr -= 1
91 mask >>= 1
92 return cidr
93
94
95
96#
97# --------------------------------------------------------------------------------
98
99def inet_ntoa(n):
100 """
101 Defina a local variant which exactly meets local needs
102 """
103 return "%s.%s.%s.%s" % ( \
104 (n & 0xff000000) >> 24, \
105 (n & 0x00ff0000) >> 16, \
106 (n & 0x0000ff00) >> 8, \
107 (n & 0x000000ff) )
108
109#
110# --------------------------------------------------------------------------------
111
112def inet_aton(ip):
113 """
114 Return an integer containing the ip address passed in.
115 Assumes the field is a quad-int
116 """
117 fields = ip.split('.')
118 return (int(fields[0]) << 24) | \
119 (int(fields[1]) << 16) | \
120 (int(fields[2]) << 8) | \
121 (int(fields[3]))
122
123#
124# --------------------------------------------------------------------------------
125
126def ip_and_mask_ntoa(ip, mask):
127 """
128 The two values are displayed either as 'any' or as in cidr format
129 or as a ip/mask depending on the value of the mask.
130 (note the leading space)
131 """
132 if ip == '0.0.0.0' and mask == '255.255.255.255':
133 return 'any '
134 n_mask = inet_aton(mask)
135 if is_power_of_two(n_mask + 1):
136 return "%s/%d " % (ip, mask_to_cidr(n_mask))
137 return "%s %s " % (ip, mask)
138
139
140#
141# --------------------------------------------------------------------------------
142
143def ip_invert_netmask(value):
144 split_bytes = value.split('.')
145 return "%s.%s.%s.%s" % (255-int(split_bytes[0]),
146 255-int(split_bytes[1]),
147 255-int(split_bytes[2]),
148 255-int(split_bytes[3]))
149
150#
151# --------------------------------------------------------------------------------
152
153def ip_and_neg_mask(ip, mask):
154 """
155 The two values are displayed either as 'any' or as in cidr format
156 or as a ip/mask depending on the value of the mask.
157 (note the leading space). This is different from ip_and_mask_ntoa
158 since the mask need to be printed an an inverted mask when the
159 mask is displayed
160 """
161 if ip == '0.0.0.0' and mask == '255.255.255.255':
162 return 'any '
163 n_mask = inet_aton(mask)
164 if is_power_of_two(n_mask + 1):
165 cidr = mask_to_cidr(n_mask)
166 if cidr:
167 return "%s/%d " % (ip, mask_to_cidr(n_mask))
168 return "%s " % ip
169 return "%s %s " % (ip, ip_invert_netmask(mask))
170
171
172#
173# --------------------------------------------------------------------------------
174
175SINGLEQUOTE_RE = re.compile(r"'")
176DOUBLEQUOTE_RE = re.compile(r'"')
177WHITESPACE_RE = re.compile(r"\s")
178
179COMMAND_DPID_RE = re.compile(r'^(([A-Fa-f\d]){2}:?){7}[A-Fa-f\d]{2}$')
180COMMAND_SEPARATORS_RE = re.compile(r'[>;|]') # XXX belongs in some global
181
182
183def quote_string(value):
184 """
185 Return a quoted version of the string when there's imbedded
186 spaces. Worst case is when the string has both single and
187 double quotes.
188 """
189
190 if SINGLEQUOTE_RE.search(value):
191 if DOUBLEQUOTE_RE.search(value):
192 new_value = []
193 for c in value:
194 if c == '"':
195 new_value.append("\\")
196 new_value.append(c)
197 return ''.join(new_value)
198 else:
199 return '"%s"' % value
200 elif (DOUBLEQUOTE_RE.search(value) or
201 WHITESPACE_RE.search(value) or
202 COMMAND_SEPARATORS_RE.search(value)):
203 return "'%s'" % value
204 else:
205 return value
206
207
208#
209# --------------------------------------------------------------------------------
210
211def add_delim(strings_list, delim):
212 """
213 Add 'delim' to each string entry in the list, typically used to add a space
214 to completion choices for entries which aren't prefixes.
215 word mean a
216
217 """
218 return [str(x) + delim for x in strings_list]
219
220
221#
222# --------------------------------------------------------------------------------
223
224def convert_case(case, value):
225 """
226 Convert value to the requested case
227 """
228 if case == None:
229 return value
230 elif case == 'lower':
231 return value.lower()
232 elif case == 'upper':
233 return value.upper()
234 return value
235
236
237TAIL_INT_RE = re.compile(r'^(.*[^0-9])(\d+)$')
238
239
240#
241# --------------------------------------------------------------------------------
242
243def trailing_integer_cmp(x,y):
244 """
245 sorted() comparison function.
246
247 Used when the two keys may possibly have trailing numbers,
248 if these are compared using a typical sort, then the trailing
249 numbers will be sorted alphabetically, not numerically. This
250 is most obvious when interfaces, for example Eth1, Eth10, Eth2
251 are sorted. Alphabetically the order is as already shown, but
252 the desired sort order is Eth1, Eth2, Eth10
253 """
254
255 def last_digit(value):
256 # only interested in sequences tailing a digit, where
257 # the first charcter isn't a digit, used to sidestep tail_int re
258 if len(value) >= 2:
259 last_char = ord(value[-1])
260 if last_char >= ord('0') and last_char <= ord('9'):
261 first_char = ord(value[0])
262 if first_char < ord('0') or first_char > ord('9'):
263 return True
264 return False
265
266 if type(x) == int and type(y) == int:
267 return cmp(x,y)
268 if last_digit(x) and last_digit(y):
269 x_m = TAIL_INT_RE.match(x)
270 y_m = TAIL_INT_RE.match(y)
271 c = cmp(x_m.group(1), y_m.group(1))
272 if c:
273 return c
274 c = cmp(int(x_m.group(2)), int(y_m.group(2)))
275 if c:
276 return c
277 else:
278 c = cmp(try_int(x), try_int(y))
279 if c != 0:
280 return c;
281
282 return 0
283
284COMPLETION_TAIL_INT_RE = re.compile(r'^([A-Za-z0-9-]*[^0-9])(\d+) $')
285
286#
287# --------------------------------------------------------------------------------
288
289def completion_trailing_integer_cmp(x,y):
290 """
291 sorted() comparison function.
292
293 This is used for completion values sorting.
294
295 This function differs from trailing_integer_cmp, since for completions,
296 the last character may be a space to indicate that the selection is
297 complete. If the last character is a space, the character ahead
298 of it is used instead.
299 """
300
301 x_v = x
302 if isinstance(x_v, tuple):
303 x_v = x_v[0]
304 if x[-1] == ' ':
305 x_v = x[:-1]
306
307 y_v = y
308 if isinstance(y_v, tuple):
309 y_v = y_v[0]
310 if y[-1] == ' ':
311 y_v = y[:-1]
312 return trailing_integer_cmp(x_v, y_v)
313
314
315#
316# --------------------------------------------------------------------------------
317
318ABERRANT_PLURAL_MAP = {
319 'appendix': 'appendices',
320 'barracks': 'barracks',
321 'cactus': 'cacti',
322 'child': 'children',
323 'criterion': 'criteria',
324 'deer': 'deer',
325 'echo': 'echoes',
326 'elf': 'elves',
327 'embargo': 'embargoes',
328 'focus': 'foci',
329 'fungus': 'fungi',
330 'goose': 'geese',
331 'hero': 'heroes',
332 'hoof': 'hooves',
333 'index': 'indices',
334 'knife': 'knives',
335 'leaf': 'leaves',
336 'life': 'lives',
337 'man': 'men',
338 'mouse': 'mice',
339 'nucleus': 'nuclei',
340 'person': 'people',
341 'phenomenon': 'phenomena',
342 'potato': 'potatoes',
343 'self': 'selves',
344 'syllabus': 'syllabi',
345 'tomato': 'tomatoes',
346 'torpedo': 'torpedoes',
347 'veto': 'vetoes',
348 'woman': 'women',
349 }
350
351VOWELS = set('aeiou')
352
353def pluralize(singular):
354 """Return plural form of given lowercase singular word (English only). Based on
355 ActiveState recipe http://code.activestate.com/recipes/413172/
356
357 >>> pluralize('')
358 ''
359 >>> pluralize('goose')
360 'geese'
361 >>> pluralize('dolly')
362 'dollies'
363 >>> pluralize('genius')
364 'genii'
365 >>> pluralize('jones')
366 'joneses'
367 >>> pluralize('pass')
368 'passes'
369 >>> pluralize('zero')
370 'zeros'
371 >>> pluralize('casino')
372 'casinos'
373 >>> pluralize('hero')
374 'heroes'
375 >>> pluralize('church')
376 'churches'
377 >>> pluralize('x')
378 'xs'
379 >>> pluralize('car')
380 'cars'
381
382 """
383 if not singular:
384 return ''
385 plural = ABERRANT_PLURAL_MAP.get(singular)
386 if plural:
387 return plural
388 root = singular
389 try:
390 if singular[-1] == 'y' and singular[-2] not in VOWELS:
391 root = singular[:-1]
392 suffix = 'ies'
393 elif singular[-1] == 's':
394 if singular[-2] in VOWELS:
395 if singular[-3:] == 'ius':
396 root = singular[:-2]
397 suffix = 'i'
398 else:
399 root = singular[:-1]
400 suffix = 'ses'
401 else:
402 suffix = 'es'
403 elif singular[-2:] in ('ch', 'sh'):
404 suffix = 'es'
405 else:
406 suffix = 's'
407 except IndexError:
408 suffix = 's'
409 plural = root + suffix
410 return plural