Commit 4dff3320 by willmcgugan

Changed syntax for commands to be more url like, optimized sftps to use fewer queries for listdir

parent f1f224c1
from fs.opener import opener from fs.opener import opener
from fs.utils import copyfile, copystructure from fs.utils import copyfile, copystructure
from fs.path import pathjoin from fs.path import pathjoin, iswildcard
from fs.errors import FSError from fs.errors import FSError
from fs.commands.runner import Command from fs.commands.runner import Command
import sys import sys
...@@ -92,7 +92,7 @@ Copy SOURCE to DESTINATION""" ...@@ -92,7 +92,7 @@ Copy SOURCE to DESTINATION"""
if src_path is None: if src_path is None:
src_path = '/' src_path = '/'
if self.is_wildcard(src_path): if iswildcard(src_path):
for file_path in src_fs.listdir(wildcard=src_path, full=True): for file_path in src_fs.listdir(wildcard=src_path, full=True):
copy_fs_paths.append((self.FILE, src_fs, file_path, file_path)) copy_fs_paths.append((self.FILE, src_fs, file_path, file_path))
......
#!/usr/bin/env python #!/usr/bin/env python
from fs.opener import opener from fs.opener import opener
from fs.path import pathsplit, abspath, isdotfile from fs.path import pathsplit, abspath, isdotfile, iswildcard
from fs.commands.runner import Command from fs.commands.runner import Command
from collections import defaultdict from collections import defaultdict
import sys import sys
...@@ -43,10 +43,10 @@ List contents of [PATH]""" ...@@ -43,10 +43,10 @@ List contents of [PATH]"""
path = path or '.' path = path or '.'
wildcard = None wildcard = None
if self.is_wildcard(path): if iswildcard(path):
path, wildcard = pathsplit(path) path, wildcard = pathsplit(path)
if fs.isfile(path): if path != '.' and fs.isfile(path):
if not options.dirsonly: if not options.dirsonly:
file_paths.append(path) file_paths.append(path)
else: else:
......
...@@ -15,6 +15,8 @@ Recursively display the contents of PATH in an ascii tree""" ...@@ -15,6 +15,8 @@ Recursively display the contents of PATH in an ascii tree"""
optparse = super(FSTree, self).get_optparse() optparse = super(FSTree, self).get_optparse()
optparse.add_option('-d', '--depth', dest='depth', type="int", default=5, optparse.add_option('-d', '--depth', dest='depth', type="int", default=5,
help="Maximum depth to display", metavar="DEPTH") help="Maximum depth to display", metavar="DEPTH")
optparse.add_option('-a', '--all', dest='all', action='store_true', default=False,
help="do not hide dot files")
return optparse return optparse
def do_run(self, options, args): def do_run(self, options, args):
...@@ -31,7 +33,8 @@ Recursively display the contents of PATH in an ascii tree""" ...@@ -31,7 +33,8 @@ Recursively display the contents of PATH in an ascii tree"""
print_fs(fs, path or '', print_fs(fs, path or '',
file_out=self.output_file, file_out=self.output_file,
max_levels=options.depth, max_levels=options.depth,
terminal_colors=self.is_terminal()) terminal_colors=self.is_terminal(),
hide_dotfiles=not options.all)
def run(): def run():
return FSTree().run() return FSTree().run()
......
...@@ -2,7 +2,7 @@ import sys ...@@ -2,7 +2,7 @@ import sys
from optparse import OptionParser from optparse import OptionParser
from fs.opener import opener, OpenerError from fs.opener import opener, OpenerError
from fs.errors import FSError from fs.errors import FSError
from fs.path import splitext, pathsplit, isdotfile from fs.path import splitext, pathsplit, isdotfile, iswildcard
import platform import platform
from collections import defaultdict from collections import defaultdict
...@@ -55,11 +55,6 @@ class Command(object): ...@@ -55,11 +55,6 @@ class Command(object):
self.terminal_width = w self.terminal_width = w
self.name = self.__class__.__name__.lower() self.name = self.__class__.__name__.lower()
def is_wildcard(self, path):
if path is None:
return False
return '*' in path or '?' in path
def is_terminal(self): def is_terminal(self):
try: try:
return self.output_file.isatty() return self.output_file.isatty()
...@@ -111,7 +106,7 @@ class Command(object): ...@@ -111,7 +106,7 @@ class Command(object):
if path is None: if path is None:
return [], [] return [], []
pathname, resourcename = pathsplit(path) pathname, resourcename = pathsplit(path)
if self.is_wildcard(resourcename): if iswildcard(resourcename):
dir_paths = fs.listdir(pathname, dir_paths = fs.listdir(pathname,
wildcard=resourcename, wildcard=resourcename,
absolute=True, absolute=True,
...@@ -137,7 +132,7 @@ class Command(object): ...@@ -137,7 +132,7 @@ class Command(object):
resources = [] resources = []
for fs, path in fs_paths: for fs, path in fs_paths:
if self.is_wildcard(path): if path and iswildcard(path):
if not files_only: if not files_only:
dir_paths = fs.listdir(wildcard=path, dirs_only=True) dir_paths = fs.listdir(wildcard=path, dirs_only=True)
for path in dir_paths: for path in dir_paths:
...@@ -227,8 +222,8 @@ class Command(object): ...@@ -227,8 +222,8 @@ class Command(object):
if self.is_terminal(): if self.is_terminal():
self.output("\n") self.output("\n")
return 0 return 0
except ValueError: #except ValueError:
pass # pass
except SystemExit: except SystemExit:
return 0 return 0
except IOError: except IOError:
......
...@@ -751,8 +751,7 @@ class FTPFS(FS): ...@@ -751,8 +751,7 @@ class FTPFS(FS):
def __init__(self, host='', user='', passwd='', acct='', timeout=_GLOBAL_DEFAULT_TIMEOUT, def __init__(self, host='', user='', passwd='', acct='', timeout=_GLOBAL_DEFAULT_TIMEOUT,
port=21, port=21,
dircache=True, dircache=True):
max_buffer_size=128*1024*1024):
""" Connect to a FTP server. """ Connect to a FTP server.
:param host: Host to connect to :param host: Host to connect to
...@@ -766,7 +765,6 @@ class FTPFS(FS): ...@@ -766,7 +765,6 @@ class FTPFS(FS):
changes to the ftp file structure will not be visible until changes to the ftp file structure will not be visible until
`~fs.ftpfs.FTPFS.clear_dircache` is called `~fs.ftpfs.FTPFS.clear_dircache` is called
:param dircache: If True directory information will be cached for fast access :param dircache: If True directory information will be cached for fast access
:param max_buffer_size: Number of bytes to hold before blocking write operations
""" """
...@@ -782,8 +780,6 @@ class FTPFS(FS): ...@@ -782,8 +780,6 @@ class FTPFS(FS):
self.use_dircache = dircache self.use_dircache = dircache
self.get_dircache() self.get_dircache()
self.max_buffer_size = max_buffer_size
self._cache_hint = False self._cache_hint = False
self._locals._ftp = None self._locals._ftp = None
self._thread_ftps = set() self._thread_ftps = set()
......
...@@ -156,7 +156,7 @@ class DirEntry(object): ...@@ -156,7 +156,7 @@ class DirEntry(object):
self.locks += 1 self.locks += 1
def unlock(self): def unlock(self):
self.locks -=1 self.locks -= 1
assert self.locks >=0, "Lock / Unlock mismatch!" assert self.locks >=0, "Lock / Unlock mismatch!"
def desc_contents(self): def desc_contents(self):
...@@ -494,7 +494,7 @@ class MemoryFS(FS): ...@@ -494,7 +494,7 @@ class MemoryFS(FS):
if dir_entry is None: if dir_entry is None:
raise ResourceNotFoundError(path) raise ResourceNotFoundError(path)
if dir_entry.isfile(): if dir_entry.isfile():
raise ResourceInvalidError(path,msg="that's a file, not a directory: %(path)s") raise ResourceInvalidError(path, msg="not a directory: %(path)s")
paths = dir_entry.contents.keys() paths = dir_entry.contents.keys()
for (i,p) in enumerate(paths): for (i,p) in enumerate(paths):
if not isinstance(p,unicode): if not isinstance(p,unicode):
...@@ -522,7 +522,7 @@ class MemoryFS(FS): ...@@ -522,7 +522,7 @@ class MemoryFS(FS):
return info return info
@synchronize @synchronize
def copydir(self, src, dst, overwrite=False, ignore_errors=False, chunk_size=16384): def copydir(self, src, dst, overwrite=False, ignore_errors=False, chunk_size=1024*64):
src_dir_entry = self._get_dir_entry(src) src_dir_entry = self._get_dir_entry(src)
if src_dir_entry is None: if src_dir_entry is None:
raise ResourceNotFoundError(src) raise ResourceNotFoundError(src)
...@@ -533,7 +533,7 @@ class MemoryFS(FS): ...@@ -533,7 +533,7 @@ class MemoryFS(FS):
dst_dir_entry.xattrs.update(src_xattrs) dst_dir_entry.xattrs.update(src_xattrs)
@synchronize @synchronize
def movedir(self, src, dst, overwrite=False, ignore_errors=False, chunk_size=16384): def movedir(self, src, dst, overwrite=False, ignore_errors=False, chunk_size=1024*64):
src_dir_entry = self._get_dir_entry(src) src_dir_entry = self._get_dir_entry(src)
if src_dir_entry is None: if src_dir_entry is None:
raise ResourceNotFoundError(src) raise ResourceNotFoundError(src)
...@@ -544,7 +544,7 @@ class MemoryFS(FS): ...@@ -544,7 +544,7 @@ class MemoryFS(FS):
dst_dir_entry.xattrs.update(src_xattrs) dst_dir_entry.xattrs.update(src_xattrs)
@synchronize @synchronize
def copy(self, src, dst, overwrite=False, chunk_size=16384): def copy(self, src, dst, overwrite=False, chunk_size=1024*64):
src_dir_entry = self._get_dir_entry(src) src_dir_entry = self._get_dir_entry(src)
if src_dir_entry is None: if src_dir_entry is None:
raise ResourceNotFoundError(src) raise ResourceNotFoundError(src)
...@@ -555,7 +555,7 @@ class MemoryFS(FS): ...@@ -555,7 +555,7 @@ class MemoryFS(FS):
dst_dir_entry.xattrs.update(src_xattrs) dst_dir_entry.xattrs.update(src_xattrs)
@synchronize @synchronize
def move(self, src, dst, overwrite=False, chunk_size=16384): def move(self, src, dst, overwrite=False, chunk_size=1024*64):
src_dir_entry = self._get_dir_entry(src) src_dir_entry = self._get_dir_entry(src)
if src_dir_entry is None: if src_dir_entry is None:
raise ResourceNotFoundError(src) raise ResourceNotFoundError(src)
...@@ -566,6 +566,27 @@ class MemoryFS(FS): ...@@ -566,6 +566,27 @@ class MemoryFS(FS):
dst_dir_entry.xattrs.update(src_xattrs) dst_dir_entry.xattrs.update(src_xattrs)
@synchronize @synchronize
def getcontents(self, path):
dir_entry = self._get_dir_entry(path)
if dir_entry is None:
raise ResourceNotFoundError(path)
if not dir_entry.isfile():
raise ResourceInvalidError(path, msg="not a directory: %(path)s")
return dir_entry.data or ''
@synchronize
def setcontents(self, path, data, chunk_size=1024*64):
if not isinstance(data, str):
return super(MemoryFS, self).setcontents(path, data, chunk_size)
if not self.exists(path):
self.open(path, 'w').close()
dir_entry = self._get_dir_entry(path)
if not dir_entry.isfile():
raise ResourceInvalidError('Not a directory %(path)s', path)
dir_entry.data = data
@synchronize
def setxattr(self, path, key, value): def setxattr(self, path, key, value):
dir_entry = self._dir_entry(path) dir_entry = self._dir_entry(path)
key = unicode(key) key = unicode(key)
......
...@@ -515,3 +515,16 @@ class PathMap(object): ...@@ -515,3 +515,16 @@ class PathMap(object):
def names(self,root="/"): def names(self,root="/"):
return list(self.iternames(root)) return list(self.iternames(root))
_wild_chars = frozenset('*?[]!{}')
def iswildcard(path):
"""Check if a path ends with a wildcard
>>> is_wildcard('foo/bar/baz.*')
True
>>> is_wildcard('foo/bar')
False
"""
assert path is not None
base_chars = frozenset(basename(path))
return not base_chars.isdisjoint(_wild_chars)
...@@ -9,12 +9,15 @@ Filesystem accessing an SFTP server (via paramiko) ...@@ -9,12 +9,15 @@ Filesystem accessing an SFTP server (via paramiko)
import datetime import datetime
import stat as statinfo import stat as statinfo
import threading import threading
import os
import paramiko import paramiko
from getpass import getuser
from binascii import hexlify
from fs.base import * from fs.base import *
from fs.path import * from fs.path import *
from fs.errors import * from fs.errors import *
from fs.utils import isdir, isfile
# SFTPClient appears to not be thread-safe, so we use an instance per thread # SFTPClient appears to not be thread-safe, so we use an instance per thread
if hasattr(threading, "local"): if hasattr(threading, "local"):
...@@ -58,6 +61,7 @@ class SFTPFS(FS): ...@@ -58,6 +61,7 @@ class SFTPFS(FS):
'atomic.setcontents' : False 'atomic.setcontents' : False
} }
def __init__(self, connection, root_path="/", encoding=None, **credentials): def __init__(self, connection, root_path="/", encoding=None, **credentials):
"""SFTPFS constructor. """SFTPFS constructor.
...@@ -88,18 +92,81 @@ class SFTPFS(FS): ...@@ -88,18 +92,81 @@ class SFTPFS(FS):
self._tlocal = thread_local() self._tlocal = thread_local()
self._transport = None self._transport = None
self._client = None self._client = None
hostname = None
if isinstance(connection, basestring):
hostname = connection
else:
try:
hostname, port = connection
except ValueError:
pass
hostkeytype = None
hostkey = None
if hostname is not None:
try:
host_keys = paramiko.util.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
except IOError:
try:
# try ~/ssh/ too, because windows can't have a folder named ~/.ssh/
host_keys = paramiko.util.load_host_keys(os.path.expanduser('~/ssh/known_hosts'))
except IOError:
host_keys = {}
if host_keys.has_key(hostname):
hostkeytype = host_keys[hostname].keys()[0]
hostkey = host_keys[hostname][hostkeytype]
credentials['hostkey'] = hostkey
if not credentials.get('username'):
credentials['username'] = getuser()
super(SFTPFS, self).__init__()
if isinstance(connection,paramiko.Channel): if isinstance(connection,paramiko.Channel):
self._transport = None self._transport = None
self._client = paramiko.SFTPClient(connection) self._client = paramiko.SFTPClient(connection)
else: else:
if not isinstance(connection,paramiko.Transport): if not isinstance(connection, paramiko.Transport):
connection = paramiko.Transport(connection) connection = paramiko.Transport(connection)
self._owns_transport = True self._owns_transport = True
try:
if not connection.is_authenticated(): if not connection.is_authenticated():
connection.connect(**credentials) connection.connect(**credentials)
if not connection.is_authenticated():
self._agent_auth(connection, credentials.get('username'))
if not connection.is_authenticated():
connection.close()
raise RemoteConnectionError('No auth')
except paramiko.AuthenticationException:
raise RemoteConnectionError('Auth rejected')
self._transport = connection self._transport = connection
self.root_path = abspath(normpath(root_path)) self.root_path = abspath(normpath(root_path))
super(SFTPFS, self).__init__()
@classmethod
def _agent_auth(cls, transport, username):
"""
Attempt to authenticate to the given transport using any of the private
keys available from an SSH agent.
"""
agent = paramiko.Agent()
agent_keys = agent.get_keys()
if len(agent_keys) == 0:
return False
for key in agent_keys:
try:
transport.auth_publickey(username, key)
return key
except paramiko.SSHException:
pass
return None
def __del__(self): def __del__(self):
self.close() self.close()
...@@ -184,6 +251,8 @@ class SFTPFS(FS): ...@@ -184,6 +251,8 @@ class SFTPFS(FS):
@convert_os_errors @convert_os_errors
def isdir(self,path): def isdir(self,path):
if path == '/':
return True
npath = self._normpath(path) npath = self._normpath(path)
try: try:
stat = self.client.stat(npath) stat = self.client.stat(npath)
...@@ -209,6 +278,10 @@ class SFTPFS(FS): ...@@ -209,6 +278,10 @@ class SFTPFS(FS):
npath = self._normpath(path) npath = self._normpath(path)
try: try:
paths = self.client.listdir(npath) paths = self.client.listdir(npath)
if dirs_only or files_only:
path_attrs = self.client.listdir_attr(npath)
else:
path_attrs = None
except IOError, e: except IOError, e:
if getattr(e,"errno",None) == 2: if getattr(e,"errno",None) == 2:
if self.isfile(path): if self.isfile(path):
...@@ -217,10 +290,72 @@ class SFTPFS(FS): ...@@ -217,10 +290,72 @@ class SFTPFS(FS):
elif self.isfile(path): elif self.isfile(path):
raise ResourceInvalidError(path,msg="Can't list directory contents of a file: %(path)s") raise ResourceInvalidError(path,msg="Can't list directory contents of a file: %(path)s")
raise raise
if path_attrs is not None:
if dirs_only:
filter_paths = []
for path, attr in zip(paths, path_attrs):
if isdir(self, path, attr.__dict__):
filter_paths.append(path)
paths = filter_paths
elif files_only:
filter_paths = []
for path, attr in zip(paths, path_attrs):
if isfile(self, path, attr.__dict__):
filter_paths.append(path)
paths = filter_paths
for (i,p) in enumerate(paths): for (i,p) in enumerate(paths):
if not isinstance(p,unicode): if not isinstance(p,unicode):
paths[i] = p.decode(self.encoding) paths[i] = p.decode(self.encoding)
return self._listdir_helper(path, paths, wildcard, full, absolute, dirs_only, files_only) return self._listdir_helper(path, paths, wildcard, full, absolute, False, False)
@convert_os_errors
def listdirinfo(self,path="./",wildcard=None,full=False,absolute=False,dirs_only=False,files_only=False):
npath = self._normpath(path)
try:
paths = self.client.listdir(npath)
attrs = self.client.listdir_attr(npath)
attrs_map = dict(zip(paths, attrs))
except IOError, e:
if getattr(e,"errno",None) == 2:
if self.isfile(path):
raise ResourceInvalidError(path,msg="Can't list directory contents of a file: %(path)s")
raise ResourceNotFoundError(path)
elif self.isfile(path):
raise ResourceInvalidError(path,msg="Can't list directory contents of a file: %(path)s")
raise
if dirs_only:
filter_paths = []
for path, attr in zip(paths, attrs):
if isdir(self, path, attr.__dict__):
filter_paths.append(path)
paths = filter_paths
elif files_only:
filter_paths = []
for path, attr in zip(paths, attrs):
if isfile(self, path, attr.__dict__):
filter_paths.append(path)
paths = filter_paths
for (i, p) in enumerate(paths):
if not isinstance(p, unicode):
paths[i] = p.decode(self.encoding)
def getinfo(p):
resourcename = basename(p)
info = attrs_map.get(resourcename)
if info is None:
return self.getinfo(pathjoin(path, p))
return self._extract_info(info.__dict__)
return [(p, getinfo(p)) for p in
self._listdir_helper(path, paths, wildcard, full, absolute, False, False)]
@convert_os_errors @convert_os_errors
def makedir(self,path,recursive=False,allow_recreate=False): def makedir(self,path,recursive=False,allow_recreate=False):
...@@ -335,6 +470,23 @@ class SFTPFS(FS): ...@@ -335,6 +470,23 @@ class SFTPFS(FS):
raise ParentDirectoryMissingError(dst,msg="Destination directory does not exist: %(path)s") raise ParentDirectoryMissingError(dst,msg="Destination directory does not exist: %(path)s")
raise raise
_info_vars = frozenset('st_size st_uid st_gid st_mode st_atime st_mtime'.split())
@classmethod
def _extract_info(cls, stats):
fromtimestamp = datetime.datetime.fromtimestamp
info = dict((k, v) for k, v in stats.iteritems() if k in cls._info_vars)
info['size'] = info['st_size']
ct = info.get('st_ctime')
if ct is not None:
info['created_time'] = fromtimestamp(ct)
at = info.get('st_atime')
if at is not None:
info['accessed_time'] = fromtimestamp(at)
mt = info.get('st_mtime')
if mt is not None:
info['modified_time'] = fromtimestamp(mt)
return info
@convert_os_errors @convert_os_errors
def getinfo(self, path): def getinfo(self, path):
npath = self._normpath(path) npath = self._normpath(path)
......
...@@ -56,10 +56,13 @@ def copyfile(src_fs, src_path, dst_fs, dst_path, overwrite=True, chunk_size=64*1 ...@@ -56,10 +56,13 @@ def copyfile(src_fs, src_path, dst_fs, dst_path, overwrite=True, chunk_size=64*1
src = None src = None
try: try:
# Chunk copy # Chunk copy
if src_fs.getsize(src_path) < chunk_size:
src = src_fs.getcontents(src_path)
else:
src = src_fs.open(src_path, 'rb') src = src_fs.open(src_path, 'rb')
dst_fs.setcontents(dst_path, src, chunk_size=chunk_size) dst_fs.setcontents(dst_path, src, chunk_size=chunk_size)
finally: finally:
if src is not None: if src is not None and hasattr(src, 'close'):
src.close() src.close()
...@@ -89,14 +92,18 @@ def movefile(src_fs, src_path, dst_fs, dst_path, overwrite=True, chunk_size=64*1 ...@@ -89,14 +92,18 @@ def movefile(src_fs, src_path, dst_fs, dst_path, overwrite=True, chunk_size=64*1
FS._shutil_movefile(src_syspath, dst_syspath) FS._shutil_movefile(src_syspath, dst_syspath)
return return
src = None src = None
try: try:
# Chunk copy
if src_fs.getsize(src_path) < chunk_size:
src = src_fs.getcontents(src_path)
else:
src = src_fs.open(src_path, 'rb') src = src_fs.open(src_path, 'rb')
dst_fs.setcontents(dst_path, src, chunk_size=chunk_size) dst_fs.setcontents(dst_path, src, chunk_size=chunk_size)
src_fs.remove(src_path) src_fs.remove(src_path)
finally: finally:
if src is not None: if src is not None and hasattr(src, 'close'):
src.close() src.close()
def movedir(fs1, fs2, overwrite=False, ignore_errors=False, chunk_size=64*1024): def movedir(fs1, fs2, overwrite=False, ignore_errors=False, chunk_size=64*1024):
...@@ -324,7 +331,7 @@ def find_duplicates(fs, ...@@ -324,7 +331,7 @@ def find_duplicates(fs,
paths = list(set(paths).difference(dups)) paths = list(set(paths).difference(dups))
def print_fs(fs, path='/', max_levels=5, file_out=None, terminal_colors=None): def print_fs(fs, path='/', max_levels=5, file_out=None, terminal_colors=None, hide_dotfiles=False):
"""Prints a filesystem listing to stdout (including sub dirs). Useful as a debugging aid. """Prints a filesystem listing to stdout (including sub dirs). Useful as a debugging aid.
Be careful about printing a OSFS, or any other large filesystem. Be careful about printing a OSFS, or any other large filesystem.
Without max_levels set, this function will traverse the entire directory tree. Without max_levels set, this function will traverse the entire directory tree.
...@@ -343,13 +350,14 @@ def print_fs(fs, path='/', max_levels=5, file_out=None, terminal_colors=None): ...@@ -343,13 +350,14 @@ def print_fs(fs, path='/', max_levels=5, file_out=None, terminal_colors=None):
:param file_out: File object to write output to (defaults to sys.stdout) :param file_out: File object to write output to (defaults to sys.stdout)
:param terminal_colors: If True, terminal color codes will be written, set to False for non-console output. :param terminal_colors: If True, terminal color codes will be written, set to False for non-console output.
The default (None) will select an appropriate setting for the platform. The default (None) will select an appropriate setting for the platform.
:param hide_dotfiles: if True, files or directories begining with '.' will be removed
""" """
if file_out is None: if file_out is None:
file_out = sys.stdout file_out = sys.stdout
file_encoding = getattr(file_out, 'encoding', 'utf-8') file_encoding = getattr(file_out, 'encoding', 'utf-8') or 'utf-8'
if terminal_colors is None: if terminal_colors is None:
if sys.platform.startswith('win'): if sys.platform.startswith('win'):
...@@ -388,12 +396,16 @@ def print_fs(fs, path='/', max_levels=5, file_out=None, terminal_colors=None): ...@@ -388,12 +396,16 @@ def print_fs(fs, path='/', max_levels=5, file_out=None, terminal_colors=None):
def print_dir(fs, path, levels=[]): def print_dir(fs, path, levels=[]):
try: try:
dir_listing = [(fs.isdir(pathjoin(path,p)), p) for p in fs.listdir(path)] dir_listing = ( [(True, p) for p in fs.listdir(path, dirs_only=True)] +
[(False, p) for p in fs.listdir(path, files_only=True)] )
except Exception, e: except Exception, e:
prefix = ''.join([('| ', ' ')[last] for last in levels]) + ' ' prefix = ''.join([('| ', ' ')[last] for last in levels]) + ' '
write(wrap_prefix(prefix[:-1] + ' ') + wrap_error("unabled to retrieve directory list (%s) ..." % str(e))) write(wrap_prefix(prefix[:-1] + ' ') + wrap_error("unabled to retrieve directory list (%s) ..." % str(e)))
return 0 return 0
if hide_dotfiles:
dir_listing = [(isdir, p) for isdir, p in dir_listing if not p.startswith('.')]
dir_listing.sort(key = lambda (isdir, p):(not isdir, p.lower())) dir_listing.sort(key = lambda (isdir, p):(not isdir, p.lower()))
for i, (is_dir, item) in enumerate(dir_listing): for i, (is_dir, item) in enumerate(dir_listing):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment