def safe_path(path, posix=False): """ Convert `path` to a safe and portable POSIX path usable on multiple OSes. The returned path is an ASCII-only byte string, resolved for relative segments and itself relative. The `path` is treated as a POSIX path if `posix` is True or as a Windows path with blackslash separators otherwise. """ # if the path is UTF, try to use unicode instead if not isinstance(path, unicode): path = as_unicode(path) path = path.strip() if not is_posixpath(path): path = as_winpath(path) posix = False path = resolve(path, posix) _pathmod, path_sep = path_handlers(path, posix) segments = [s.strip() for s in path.split(path_sep) if s.strip()] segments = [portable_filename(s) for s in segments] # print('safe_path: orig:', orig_path, 'segments:', segments) if not segments: return '_' # always return posix sep = u'/' if isinstance(path, unicode) else b'/' path = sep.join(segments) return as_posixpath(path)
def resolve(path, posix=True): """ Return a resolved relative POSIX path from `path` where extra slashes including leading and trailing slashes are removed, dot '.' and dotdot '..' path segments have been removed or resolved as possible. When a dotdot path segment cannot be further resolved and would be "escaping" from the provided path "tree", it is replaced by the string 'dotdot'. The `path` is treated as a POSIX path if `posix` is True (default) or as a Windows path with blackslash separators otherwise. """ is_unicode = isinstance(path, unicode) dot = is_unicode and u'.' or b'.' if not path: return dot path = path.strip() if not path: return dot if not is_posixpath(path): path = as_winpath(path) posix = False pathmod, path_sep = path_handlers(path, posix) path = path.strip(path_sep) segments = [s.strip() for s in path.split(path_sep) if s.strip()] # remove empty (// or ///) or blank (space only) or single dot segments segments = [s for s in segments if s and s != dot] path = path_sep.join(segments) # resolves . dot, .. dotdot path = pathmod.normpath(path) segments = path.split(path_sep) # remove empty or blank segments segments = [s.strip() for s in segments if s and s.strip()] # is this a windows absolute path? if yes strip the colon to make this relative if segments and len(segments[0]) == 2 and segments[0].endswith(':'): segments[0] = segments[0][:-1] # replace any remaining (usually leading) .. segment with a literal "dotdot" dotdot = is_unicode and u'dotdot' or b'dotdot' dd = is_unicode and u'..' or b'..' segments = [dotdot if s == dd else s for s in segments if s] if segments: path = path_sep.join(segments) else: path = dot path = as_posixpath(path) return path
def path_handlers(path, posix=True): """ Return a path module and path separator to use for handling (e.g. split and join) `path` using either POSIX or Windows conventions depending on the `path` content. Force usage of POSIX conventions if `posix` is True. """ # determine if we use posix or windows path handling is_posix = is_posixpath(path) use_posix = posix or is_posix pathmod = use_posix and posixpath or ntpath path_sep = POSIX_PATH_SEP if use_posix else WIN_PATH_SEP path_sep = isinstance(path, unicode) and unicode(path_sep) or path_sep return pathmod, path_sep