]> arthur.barton.de Git - bup.git/blobdiff - lib/bup/helpers.py
Fix typo in documentation for strip_base_path
[bup.git] / lib / bup / helpers.py
index 352485951a5684d1b2fbcde206ed27430d38cd4f..36b95602e9c8f2088ec9fd55d9693f47619b1d4f 100644 (file)
@@ -1,7 +1,32 @@
 """Helper functions and classes for bup."""
+
 import sys, os, pwd, subprocess, errno, socket, select, mmap, stat, re
 from bup import _version
 
+# This function should really be in helpers, not in bup.options.  But we
+# want options.py to be standalone so people can include it in other projects.
+from bup.options import _tty_width
+tty_width = _tty_width
+
+
+def atoi(s):
+    """Convert the string 's' to an integer. Return 0 if s is not a number."""
+    try:
+        return int(s or '0')
+    except ValueError:
+        return 0
+
+
+def atof(s):
+    """Convert the string 's' to a float. Return 0 if s is not a number."""
+    try:
+        return float(s or '0')
+    except ValueError:
+        return 0
+
+
+buglvl = atoi(os.environ.get('BUP_DEBUG', 0))
+
 
 # Write (blockingly) to sockets that may or may not be in blocking mode.
 # We need this because our stderr is sometimes eaten by subprocesses
@@ -26,14 +51,27 @@ def log(s):
     _hard_write(sys.stderr.fileno(), s)
 
 
-def mkdirp(d):
+def debug1(s):
+    if buglvl >= 1:
+        log(s)
+
+
+def debug2(s):
+    if buglvl >= 2:
+        log(s)
+
+
+def mkdirp(d, mode=None):
     """Recursively create directories on path 'd'.
 
     Unlike os.makedirs(), it doesn't raise an exception if the last element of
     the path already exists.
     """
     try:
-        os.makedirs(d)
+        if mode:
+            os.makedirs(d, mode)
+        else:
+            os.makedirs(d)
     except OSError, e:
         if e.errno == errno.EEXIST:
             pass
@@ -247,6 +285,11 @@ def _mmap_do(f, sz, flags, prot):
     if not sz:
         st = os.fstat(f.fileno())
         sz = st.st_size
+    if not sz:
+        # trying to open a zero-length map gives an error, but an empty
+        # string has all the same behaviour of a zero-length map, ie. it has
+        # no elements :)
+        return ''
     map = mmap.mmap(f.fileno(), sz, flags, prot)
     f.close()  # map will persist beyond file close
     return map
@@ -302,14 +345,6 @@ def count(l):
     return reduce(lambda x,y: x+1, l)
 
 
-def atoi(s):
-    """Convert the string 's' to an integer. Return 0 if s is not a number."""
-    try:
-        return int(s or '0')
-    except ValueError:
-        return 0
-
-
 saved_errors = []
 def add_error(e):
     """Append an error message to the list of saved errors.
@@ -348,9 +383,11 @@ def columnate(l, prefix):
     The number of columns is determined automatically based on the string
     lengths.
     """
+    if not l:
+        return ""
     l = l[:]
     clen = max(len(s) for s in l)
-    ncols = (78 - len(prefix)) / (clen + 2)
+    ncols = (tty_width() - len(prefix)) / (clen + 2)
     if ncols <= 1:
         ncols = 1
         clen = 0
@@ -365,6 +402,59 @@ def columnate(l, prefix):
         out += prefix + ''.join(('%-*s' % (clen+2, s)) for s in row) + '\n'
     return out
 
+def parse_date_or_fatal(str, fatal):
+    """Parses the given date or calls Option.fatal().
+    For now we expect a string that contains a float."""
+    try:
+        date = atof(str)
+    except ValueError, e:
+        raise fatal('invalid date format (should be a float): %r' % e)
+    else:
+        return date
+
+def strip_path(prefix, path):
+    """Strips a given prefix from a path.
+
+    First both paths are normalized.
+
+    Raises an Exception if no prefix is given.
+    """
+    if prefix == None:
+        raise Exception('no path given')
+
+    normalized_prefix = os.path.realpath(prefix)
+    debug2("normalized_prefix: %s\n" % normalized_prefix)
+    normalized_path = os.path.realpath(path)
+    debug2("normalized_path: %s\n" % normalized_path)
+    if normalized_path.startswith(normalized_prefix):
+        return normalized_path[len(normalized_prefix):]
+    else:
+        return path
+
+def strip_base_path(path, base_paths):
+    """Strips the base path from a given path.
+
+
+    Determines the base path for the given string and then strips it
+    using strip_path().
+    Iterates over all base_paths from long to short, to prevent that
+    a too short base_path is removed.
+    """
+    normalized_path = os.path.realpath(path)
+    sorted_base_paths = sorted(base_paths, key=len, reverse=True)
+    for bp in sorted_base_paths:
+        if normalized_path.startswith(os.path.realpath(bp)):
+            return strip_path(bp, normalized_path)
+    return path
+
+def graft_path(graft_points, path):
+    normalized_path = os.path.realpath(path)
+    for graft_point in graft_points:
+        old_prefix, new_prefix = graft_point
+        if normalized_path.startswith(old_prefix):
+            return re.sub(r'^' + old_prefix, new_prefix, normalized_path)
+    return normalized_path
+
 
 # hashlib is only available in python 2.5 or higher, but the 'sha' module
 # produces a DeprecationWarning in python 2.6 or higher.  We want to support