_PARSEUNIT_REGEX = re.compile(r"^([.\d]+)\s*([a-zA-Z]+)?$")
#: Characters which don't need to be quoted for shell commands
-_SHELL_UNQUOTED_RE = re.compile('^[-.,=:/_+@A-Za-z0-9]+$')
+_SHELL_UNQUOTED_RE = re.compile("^[-.,=:/_+@A-Za-z0-9]+$")
#: MAC checker regexp
_MAC_CHECK_RE = re.compile("^([0-9a-f]{2}:){5}[0-9a-f]{2}$", re.I)
#: Shell param checker regexp
_SHELLPARAM_REGEX = re.compile(r"^[-a-zA-Z0-9._+/:%@]+$")
+#: ASCII equivalent of unicode character 'HORIZONTAL ELLIPSIS' (U+2026)
+_ASCII_ELLIPSIS = "..."
+
def MatchNameComponent(key, name_list, case_sensitive=True):
"""Try to match a name against a list.
if not case_sensitive:
re_flags |= re.IGNORECASE
key = key.upper()
- mo = re.compile("^%s(\..*)?$" % re.escape(key), re_flags)
+
+ name_re = re.compile(r"^%s(\..*)?$" % re.escape(key), re_flags)
+
names_filtered = []
string_matches = []
for name in name_list:
- if mo.match(name) is not None:
+ if name_re.match(name) is not None:
names_filtered.append(name)
if not case_sensitive and key == name.upper():
string_matches.append(name)
return string_matches[0]
if len(names_filtered) == 1:
return names_filtered[0]
+
return None
+def _DnsNameGlobHelper(match):
+ """Helper function for L{DnsNameGlobPattern}.
+
+ Returns regular expression pattern for parts of the pattern.
+
+ """
+ text = match.group(0)
+
+ if text == "*":
+ return "[^.]*"
+ elif text == "?":
+ return "[^.]"
+ else:
+ return re.escape(text)
+
+
+def DnsNameGlobPattern(pattern):
+ """Generates regular expression from DNS name globbing pattern.
+
+ A DNS name globbing pattern (e.g. C{*.site}) is converted to a regular
+ expression. Escape sequences or ranges (e.g. [a-z]) are not supported.
+
+ Matching always starts at the leftmost part. An asterisk (*) matches all
+ characters except the dot (.) separating DNS name parts. A question mark (?)
+ matches a single character except the dot (.).
+
+ @type pattern: string
+ @param pattern: DNS name globbing pattern
+ @rtype: string
+ @return: Regular expression
+
+ """
+ return r"^%s(\..*)?$" % re.sub(r"\*|\?|[^*?]*", _DnsNameGlobHelper, pattern)
+
+
def FormatUnit(value, units):
"""Formats an incoming number of MiB with the appropriate unit.
@return: the formatted value (with suffix)
"""
- if units not in ('m', 'g', 't', 'h'):
+ if units not in ("m", "g", "t", "h"):
raise errors.ProgrammerError("Invalid unit specified '%s'" % str(units))
- suffix = ''
+ suffix = ""
- if units == 'm' or (units == 'h' and value < 1024):
- if units == 'h':
- suffix = 'M'
+ if units == "m" or (units == "h" and value < 1024):
+ if units == "h":
+ suffix = "M"
return "%d%s" % (round(value, 0), suffix)
- elif units == 'g' or (units == 'h' and value < (1024 * 1024)):
- if units == 'h':
- suffix = 'G'
+ elif units == "g" or (units == "h" and value < (1024 * 1024)):
+ if units == "h":
+ suffix = "G"
return "%0.1f%s" % (round(float(value) / 1024, 1), suffix)
else:
- if units == 'h':
- suffix = 'T'
+ if units == "h":
+ suffix = "T"
return "%0.1f%s" % (round(float(value) / 1024 / 1024, 1), suffix)
if unit:
lcunit = unit.lower()
else:
- lcunit = 'm'
+ lcunit = "m"
- if lcunit in ('m', 'mb', 'mib'):
+ if lcunit in ("m", "mb", "mib"):
# Value already in MiB
pass
- elif lcunit in ('g', 'gb', 'gib'):
+ elif lcunit in ("g", "gb", "gib"):
value *= 1024
- elif lcunit in ('t', 'tb', 'tib'):
+ elif lcunit in ("t", "tb", "tib"):
value *= 1024 * 1024
else:
"""
INDENT_STR = " "
- def __init__(self, fh):
+ def __init__(self, fh, indent=True):
"""Initializes this class.
"""
self._fh = fh
+ self._indent_enabled = indent
self._indent = 0
def IncIndent(self):
"""
assert self._indent >= 0
- self._fh.write(self._indent * self.INDENT_STR)
-
if args:
- self._fh.write(txt % args)
+ line = txt % args
else:
- self._fh.write(txt)
+ line = txt
+
+ if line and self._indent_enabled:
+ # Indent only if there's something on the line
+ self._fh.write(self._indent * self.INDENT_STR)
+
+ self._fh.write(line)
self._fh.write("\n")
"""
if isinstance(text, unicode):
# only if unicode; if str already, we handle it below
- text = text.encode('ascii', 'backslashreplace')
+ text = text.encode("ascii", "backslashreplace")
resu = ""
for char in text:
c = ord(char)
- if char == '\t':
- resu += r'\t'
- elif char == '\n':
- resu += r'\n'
- elif char == '\r':
+ if char == "\t":
+ resu += r"\t"
+ elif char == "\n":
+ resu += r"\n"
+ elif char == "\r":
resu += r'\'r'
elif c < 32 or c >= 127: # non-printable
resu += "\\x%02x" % (c & 0xff)
e1 = slist.pop(0)
if e1.endswith("\\"):
num_b = len(e1) - len(e1.rstrip("\\"))
- if num_b % 2 == 1:
+ if num_b % 2 == 1 and slist:
e2 = slist.pop(0)
- # here the backslashes remain (all), and will be reduced in
- # the next step
- rlist.append(e1 + sep + e2)
+ # Merge the two elements and push the result back to the source list for
+ # revisiting. If e2 ended with backslashes, further merging may need to
+ # be done.
+ slist.insert(0, e1 + sep + e2)
continue
+ # here the backslashes remain (all), and will be reduced in the next step
rlist.append(e1)
# finally, replace backslash-something with something
rlist = [re.sub(r"\\(.)", r"\1", v) for v in rlist]
return ", ".join([str(val) for val in names])
-def FormatTime(val):
+def FormatTime(val, usecs=None):
"""Formats a time value.
@type val: float or None
"""
if val is None or not isinstance(val, (int, float)):
return "N/A"
+
# these two codes works on Linux, but they are not guaranteed on all
# platforms
- return time.strftime("%F %T", time.localtime(val))
+ result = time.strftime("%F %T", time.localtime(val))
+
+ if usecs is not None:
+ result += ".%06d" % usecs
+
+ return result
def FormatSeconds(secs):
if args:
# Python 2.4 doesn't have functools.partial yet
self._line_fn = \
- lambda line: line_fn(line, *args) # pylint: disable-msg=W0142
+ lambda line: line_fn(line, *args) # pylint: disable=W0142
else:
self._line_fn = line_fn
suffix = "th"
return "%s%s" % (value, suffix)
+
+
+def Truncate(text, length):
+ """Truncate string and add ellipsis if needed.
+
+ @type text: string
+ @param text: Text
+ @type length: integer
+ @param length: Desired length
+ @rtype: string
+ @return: Truncated text
+
+ """
+ assert length > len(_ASCII_ELLIPSIS)
+
+ # Serialize if necessary
+ if not isinstance(text, basestring):
+ text = str(text)
+
+ if len(text) <= length:
+ return text
+ else:
+ return text[:length - len(_ASCII_ELLIPSIS)] + _ASCII_ELLIPSIS
+
+
+def FilterEmptyLinesAndComments(text):
+ """Filters empty lines and comments from a line-based string.
+
+ Whitespace is also removed from the beginning and end of all lines.
+
+ @type text: string
+ @param text: Input string
+ @rtype: list
+
+ """
+ return [line for line in map(lambda s: s.strip(), text.splitlines())
+ # Ignore empty lines and comments
+ if line and not line.startswith("#")]