Source code for dammit.utils
# Copyright (C) 2015-2018 Camille Scott
# All rights reserved.
#
# This software may be modified and distributed under the terms
# of the BSD license. See the LICENSE file for details.
from functools import wraps
import os
import stat
import sys
from doit.action import PythonAction
from doit.task import Task, InvalidTask
import six
[docs]def cleaned_actions(actions):
'''Get a cleanup list of actions: Python actions
have their <locals> portion stripped, which clutters
up PythonActions that are closures.
'''
txt = ''
for action in actions:
txt_rep = six.text_type(action)
if isinstance(action, PythonAction):
# clean up inner fuctions in Python actions
txt_rep = txt_rep.replace('<locals>.', '')
else:
txt_rep = txt_rep[:5] + '`' + txt_rep[5:] + '`'
txt += "\n * {0}".format(txt_rep)
return txt
[docs]class DammitTask(Task):
'''Subclass doit.task.Task for dammit. Updates the string __repr__
and adds a uniform updated title function.
'''
def __repr__(self):
return '{{ DammitTask: {name}'\
'\n actions: {actions}'\
'\n file_dep: {file_dep}'\
'\n task_dep: {task_dep}'\
'\n targets: {targets} }}'.format(actions=self.actions,
**vars(self))
[docs] def title(self):
if self.custom_title:
return self.custom_title(self)
else:
if self.actions:
title = cleaned_actions(self.actions)
else:
title = "Group: %s" % ", ".join(self.task_dep)
return "%s: %s"% (self.name, title)
[docs]def dict_to_task(task_dict):
'''Given a doit task dict, return a DammitTask.
Args:
task_dict (dict): A doit task dict.
Returns:
DammitTask: Subclassed doit task.
'''
if 'actions' not in task_dict:
raise InvalidTask("Task %s must contain 'actions' field. %s" %
(task_dict['name'], task_dict))
task_attrs = list(task_dict.keys())
valid_attrs = set(Task.valid_attr.keys())
for key in task_attrs:
if key not in valid_attrs:
raise InvalidTask("Task %s contains invalid field: '%s'"%
(task_dict['name'], key))
return DammitTask(**task_dict)
[docs]def doit_task(task_dict_func):
'''Wrapper to decorate functions returning pydoit
Task dictionaries and have them return pydoit Task
objects
'''
@wraps(task_dict_func)
def d_to_t(*args, **kwargs):
task_dict = task_dict_func(*args, **kwargs)
return dict_to_task(task_dict)
return d_to_t
[docs]def touch(filename):
'''Perform the equivalent of bash's touch on the file.
Args:
filename (str): File path to touch.
'''
open(filename, 'a').close()
[docs]class Move(object):
'''Context manager to change current working directory.
'''
def __init__(self, target, create=False, verbose=False):
'''Move to specified directory.
Args:
target (str): Directory to change to.
create (bool): If True, create the directory.
'''
self.verbose = verbose
self.target = target
self.create = create
def __enter__(self):
self.cwd = os.getcwd()
if self.verbose:
print('Move to `{0}` from cwd: `{1}`'.format(self.target,
self.cwd,
file=sys.stderr))
if self.create:
try:
os.mkdir(self.target)
except OSError:
pass
os.chdir(self.target)
def __exit__(self, exc_type, exc_value, traceback):
os.chdir(self.cwd)
if exc_type:
return False
[docs]def which(program):
'''Checks whether the given program (or program path) is valid and
executable.
NOTE: Sometimes copypasta is okay! This function came from stackoverflow:
http://stackoverflow.com/a/377028/5109965
Args:
program (str): Either a program name or full path to a program.
Returns:
Return the path to the executable or None if not found
'''
def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
fpath, fname = os.path.split(program)
if fpath:
if is_exe(program):
return program
else:
for path in os.environ["PATH"].split(os.pathsep):
path = path.strip('"')
exe_file = os.path.join(path, program)
if is_exe(exe_file):
return exe_file
return None