"""A set of tools for managing paths.
The `path_tools` module provides auxiliary methods for converting between
different path names. It's main function is to convert path names indicated
relative to the Transport directory to absolute path names.
"""
import logging
import os
import re
from src import about, settings
_EXTS_DATA = settings.EXTS_DATA
_EXTS_PARAMETERS = settings.EXTS_PARAMETERS
_EXTS_IMAGE = settings.EXTS_IMAGE
DATA_DIR = about.DATA_FOLDER
HOME_DIR = about.APP_FOLDER
HOME_PATH = []
[docs]def flatten(nested):
"""
Takes a nested list and returns a flattened list. The output is always a
single-level list, and the input can be a tuple, a list, a string, an
uncombined sequence of strings, or any nested combination of these.
Parameters
----------
nested : sequence
A (possibly) nested sequence of strings. It can be entered as a list,
a single string, a tuple, or simply a comma-separated series of
arguments.
Returns
-------
list
A single-level list of the same elements.
"""
try:
try:
nested + ''
except TypeError:
pass
else:
raise TypeError
for sublist in nested:
for element in flatten(sublist):
yield element
except TypeError:
yield nested
[docs]def unrel(*args, **kwargs):
"""Expand a relative path into an absolute path.
Merges the components in args (which may be a list, a tuple, or simply a
comma-separated series) and assembles a full path, assuming they are paths
relative to the project root.
Parameters
----------
args : sequence
A (possibly) nested sequence of strings representing path elements,
relative to the root of the Transport project tree. It can be entered
as a list, a single string, a tuple, or simply a comma-separated series
of arguments.
Returns
-------
str
The absolute path of the specified file.
"""
if 'sep' in kwargs:
sep = kwargs['sep']
else:
sep = '/'
fullList = HOME_PATH + list(flatten(list(args)))
ans = sep.join(fullList)
return ans
[docs]def rel(path, asList=False):
"""Convert an absolute path to one relative to the project home.
Parameters
----------
path : str
The absolute path to convert to a relative path.
asList : bool
Whether to return the result as a list. If `False`, the path will be
returned as a string. The default is `False`.
Returns
-------
str or list(str)
The path relative to the project home. If `asList` is `True`, the result
is a list of path components. If it is `False`, the result is a relative
path string. If `path` is not a child of the project home, `None` is
returned.
"""
pathList = splitPath(path)
homePosition = len(HOME_PATH) - 1
if len(pathList) < homePosition or pathList[homePosition] != HOME_DIR:
return None
relativeList = pathList[homePosition + 1:]
if asList:
return relativeList
return '/'.join(relativeList)
[docs]def pathToImportString(path, isRelative=True, importItem=None, importFrom=True):
"""Convert a path to a string suitable for importing.
Parameters
----------
path : str
The path of a python module.
isRelative : bool
Whether `path` is relative to the project home. If `False`, `path` will
be taken to be an absolute path. The default is `True`.
importItem : str
The name of the item to be imported.
importFrom : bool
If `importItem` is `None`: Whether the import string should be of the
form ``from [package] import [module]``. If `False`, the import string
will be of the form ``import [package].[module]``.
If `importItem` is **not** `None`: whether the import string should be
of the form ``from [package].[module] import [importItem]``. If `False`,
the import string will be of the form
``import [package].[module].[importItem]``.
The default is `True`.
Returns
-------
tuple (str, str)
A two element tuple where the first element is a string containing
the requested components separated by periods, and the second is a
string which can be passed to `exec` to actually perform the import.
"""
element0 = ''
element1 = ''
if path.endswith('.py'):
path = path.replace('.py', '')
if isRelative:
pathList = splitPath(path)
else:
pathList = rel(path, True)
if importItem is None:
element0 = '.'.join(pathList)
if importFrom:
element1 = ('from ' + '.'.join(pathList[:-1]) +
' import ' + pathList[-1])
else:
element1 = 'import ' + element0
else:
element0 = '.'.join(pathList + [importItem])
if importFrom:
element1 = ('from ' + '.'.join(pathList) +
' import ' + importItem)
else:
element1 = 'import ' + element0
return (element0, element1)
[docs]def normalizePath(path):
"""Replace all backslashes with front-slashes.
Parameters
----------
path : str
A file path to normalize.
Returns
-------
str
The path with all front-slashes.
"""
answer = os.path.normpath(path)
while '\\' in answer or '//' in answer:
answer = answer.replace('\\', '/')
answer = answer.replace('//', '/')
return answer
[docs]def splitPath(path):
"""Return the path split into its components.
Parameters
----------
path : str
A file path.
Returns
-------
list of str
A list of strings representing the components of `path`.
"""
return normalizePath(path).split('/')
[docs]def lsAbsolute(directory, filesOnly=False):
"""List the contents of a directory specified by an absolute path.
Parameters
----------
directory : str
The absolute path of the directory to list.
filesOnly : bool
Whether to ignore subdirectories of `directory`.
Returns
-------
list of str
The contents of `directory` as a list of strings.
"""
os.path.isabs(directory)
if filesOnly:
return [f for f in os.listdir(directory) if
os.path.isfile(os.path.join(directory, f))]
return [f for f in os.listdir(directory)]
[docs]def ls(directory, filesOnly=False):
"""List the contents of a directory specified relative to the project home.
Parameters
----------
directory : str
The relative (to the project home) path of the directory to list.
filesOnly : bool
Whether to ignore subdirectories of `directory`.
Returns
-------
list(str)
The contents of `directory` as a list of strings.
"""
directory = unrel(directory)
return lsAbsolute(directory, filesOnly)
[docs]def getNextScan(directory):
"""Determine the next scan number.
Determine the next unused scan number, assuming that scans are indicated
by 'sNNN' or 'sNNNN', where N is a digit.
Parameters
----------
directory : str
The absolute path of the directory of data files.
Returns
-------
str
A string representing the next scan number. It's format is "sNNN",
where N is an integer.
"""
pat = '^s[0-9]{3,4}'
numbers = []
for item in lsAbsolute(directory, True):
match = re.search(pat, item)
if match != None:
numbers.append(int(match.group(0)[1:]))
if len(numbers) == 0:
return 's000'
return 's%03.u' % (max(numbers) + 1)
[docs]def appendDigitsAsNecessary(folder, basename, extension='xdat'):
"""Append digits to avoid filename clashes.
Append incrementally larger digits to the name of a file until the name
does not collide with existing files.
Parameters
----------
folder : str
The folder in which the data file will be saved. It should **not**
include a trailing slash.
basename : str
The base name of the data file. It should include a scan number, if
applicable, but it should **not** include an extension.
extension : str
The intended extension of the filename. It should not include a
leading period.
Returns
-------
str
The base name for the data file with appropriate digits added and
no extension.
"""
extensions = _EXTS_DATA + _EXTS_PARAMETERS + _EXTS_IMAGE
def checkExistance(fileToCheck):
"""Return whether a file exists."""
for ext in extensions:
if os.path.exists('%s/%s.%s' % (folder, fileToCheck, ext)):
return True
return False
log = logging.getLogger('transport')
log.info('Checking for and avoiding filename collisions.')
extensionfree = basename
filename = os.sep.join([folder, basename]) + '.' + extension
cont = checkExistance(extensionfree)
if cont:
log.warn('File ' + filename + ' exists. Appending digits.')
num = 0
while cont:
extensionfree = basename + str(num)
filename = os.sep.join([folder, extensionfree]) + '.' + extension
cont = checkExistance(extensionfree)
if cont:
log.warn('File ' + filename + ' exists. Appending digits.')
else:
log.warn('File ' + filename + ' does not exist. ' +
'Finished collision avoidance.')
num += 1
return extensionfree
[docs]def getFilesPostprocessor():
"""Return a dictionary containing data about postprocessor scripts.
Returns
-------
dict
A dictionary in which the keys are strings representing an absolute
filename without an extension. The value for each key is a dictionary
with three keys: 'py', 'pyc', 'pyo'. Each of the values is a `bool`
specifying whether the base file with the corresponding extension
exists.
"""
data = {}
projectDir = unrel('lib', 'postprocessors')
if os.name.lower().startswith('posix'):
userDir = os.path.expanduser('~/.%s/lib/postprocessors')
else:
userDir = os.path.expanduser('~/AppData/Local/%s/lib/postprocessors')
if os.path.exists(projectDir) and os.path.isdir(projectDir):
contents = lsAbsolute(projectDir, True)
for newFile in contents:
current = os.path.join(projectDir, _chopExtension(newFile))
if current is not None and current not in data:
data[current] = {'py': os.path.exists(current + '.py'),
'pyc': os.path.exists(current + '.pyc'),
'pyo': os.path.exists(current + '.pyo')}
if os.path.exists(userDir) and os.path.isdir(userDir):
contents = lsAbsolute(userDir, True)
for newFile in contents:
current = os.path.join(userDir, _chopExtension(newFile))
if current is not None and current not in data:
data[current] = {'py': os.path.exists(current + '.py'),
'pyc': os.path.exists(current + '.pyc'),
'pyo': os.path.exists(current + '.pyo')}
return data
def _chopExtension(filename):
"""Remove a .py, .pyo, or .pyc extension from a filename."""
if filename.endswith('.py'):
return filename[:-3]
if filename.endswith('.pyc') or filename.endswith('.pyo'):
return filename[:-4]
return None
def _locateDirectories():
"""Find the directories for various files to supplement the software."""
def checkDirectory(dirPath):
"""Check whether a given path exists and is writable."""
return os.path.exists(dirPath) and os.access(dirPath, os.W_OK)
print "Finding directories"
# configuration files
if os.name.lower().startswith('posix'):
confOpts = [os.path.expanduser('~/.%s/etc' % DATA_DIR),
os.path.abspath('/etc/%s' % DATA_DIR),
unrel('etc')]
libOpts = [os.path.expanduser('~/.%s/lib' % DATA_DIR),
os.path.abspath('/lib/%s' % DATA_DIR),
unrel('lib')]
subLibs = ['instruments', 'postprocessors', 'premades']
confdir = None
for opt in confOpts:
print opt
if checkDirectory(opt):
confdir = opt
break
if confdir is None:
os.makedirs(confOpts[0])
libdir = None
for opt in libOpts:
if checkDirectory(opt):
libdir = opt
break
if libdir is None:
libdir = libOpts[0]
os.makedirs(libdir)
for sub in subLibs:
fullPath = os.path.join(libdir, sub)
os.makedirs(fullPath)
else:
for sub in subLibs:
fullPath = os.path.join(libdir, sub)
try:
os.makedirs(fullPath)
except OSError:
pass
print 'Configuration directory = ' + confdir
print 'Library directory = ' + libdir
[docs]def createHomePathOld():
"""Create the home path the old way if necessary."""
myPath = splitPath(__file__)
HOME_PATH.append(myPath.pop(0))
while HOME_PATH[-1] != HOME_DIR:
HOME_PATH.append(myPath.pop(0))
[docs]def createHomePath():
"""Create the home path if necessary."""
myPath = splitPath(__file__)
for component in myPath[:-3]:
HOME_PATH.append(component)
if len(HOME_PATH) == 0:
createHomePath()
# _locateDirectories()