/usr/share/pyshared/doit/cmd_base.py is in python-doit 0.24.0-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 | import inspect
import sys
import doit
from .cmdparse import CmdOption, CmdParse
from .exceptions import InvalidCommand, InvalidDodoFile
from .dependency import backend_map
from . import loader
def version_tuple(ver_in):
"""convert a version string or tuple into a 3-element tuple with ints
Any part that is not a number (dev0, a2, b4) will be converted to -1
"""
result = []
parts = ver_in.split('.') if isinstance(ver_in, str) else ver_in
for rev in parts:
try:
result.append(int(rev))
except:
result.append(-1)
assert len(result) == 3
return result
class Command(object):
"""third-party should subclass this for commands that do no use tasks
:cvar name: (str) name of sub-cmd to be use from cmdline
:cvar doc_purpose: (str) single line cmd description
:cvar doc_usage: (str) describe accepted parameters
:cvar doc_description: (str) long description/help for cmd
:cvar cmd_options:
(list of dict) see cmdparse.CmdOption for dict format
"""
CMD_LIST = [] # register with the name of all created commands
# if not specified uses the class name
name = None
# doc attributes, should be sub-classed
doc_purpose = ''
doc_usage = ''
doc_description = None # None value will completely ommit line from doc
# sequence of dicts
cmd_options = tuple()
def __init__(self):
self.name = self.name or self.__class__.__name__.lower()
Command.CMD_LIST.append(self.name)
self.options = self.set_options()
def set_options(self):
"""@reutrn list of CmdOption
"""
opt_list = self.cmd_options
return [CmdOption(opt) for opt in opt_list]
def execute(self, opt_values, pos_args): # pragma: no cover
"""execute command
:param opt_values: (dict) with cmd_options values
:param pos_args: (list) of cmd-line positinal arguments
"""
raise NotImplementedError()
def parse_execute(self, in_args):
"""helper. just parse parameters and execute command
@args: see method parse
@returns: result of self.execute
"""
params, args = CmdParse(self.options).parse(in_args)
return self.execute(params, args)
def help(self):
"""return help text"""
text = []
text.append("Purpose: %s" % self.doc_purpose)
text.append("Usage: doit %s %s" % (self.name, self.doc_usage))
text.append('')
text.append("Options:")
for opt in self.options:
text.extend(opt.help_doc())
if self.doc_description is not None:
text.append("")
text.append("Description:")
text.append(self.doc_description)
return "\n".join(text)
######################################################################
# choose internal dependency file.
opt_depfile = {'name': 'dep_file',
'short':'',
'long': 'db-file',
'type': str,
'default': ".doit.db",
'help': "file used to save successful runs"
}
# dependency file DB backend
opt_backend = {
'name': 'backend',
'short':'',
'long': 'backend',
'type': str,
'default': "dbm",
'help': ("Select dependency file backend." +
"Available options dbm, json, sqlite3. [default: %(default)s]")
}
#### options related to dodo.py
# select dodo file containing tasks
opt_dodo = {'name': 'dodoFile',
'short':'f',
'long': 'file',
'type': str,
'default': 'dodo.py',
'help':"load task from dodo FILE [default: %(default)s]"
}
# cwd
opt_cwd = {'name': 'cwdPath',
'short':'d',
'long': 'dir',
'type': str,
'default': None,
'help':("set path to be used as cwd directory (file paths on " +
"dodo file are relative to dodo.py location.")
}
# seek dodo file on parent folders
opt_seek_file = {'name': 'seek_file',
'short': 'k',
'long': 'seek-file',
'type': bool,
'default': False,
'help': ("seek dodo file on parent folders " +
"[default: %(default)s]")
}
class TaskLoader(object):
"""task-loader interface responsible of creating Task objects
Subclasses must implement the method `load_tasks`
:cvar cmd_options:
(list of dict) see cmdparse.CmdOption for dict format
"""
cmd_options = ()
def load_tasks(self, cmd, opt_values, pos_args): # pragma: no cover
"""load tasks and DOIT_CONFIG
:return: (tuple) list of Task, dict with DOIT_CONFIG options
:param cmd: (doit.cmd_base.Command) current command being executed
:param opt_values: (dict) with values for cmd_options
:param pos_args: (list str) positional arguments from command line
"""
raise NotImplementedError()
@staticmethod
def _load_from_module(module, cmd_list):
"""load task from a module or dict with module members"""
if inspect.ismodule(module):
members = dict(inspect.getmembers(module))
else:
members = module
task_list = loader.load_tasks(members, cmd_list)
doit_config = loader.load_doit_config(members)
return task_list, doit_config
class ModuleTaskLoader(TaskLoader):
"""load tasks from a module/dictionary containing task generators
Usage: `ModuleTaskLoader(my_module)` or `ModuleTaskLoader(globals())`
"""
cmd_options = ()
def __init__(self, mod_dict):
self.mod_dict = mod_dict
def load_tasks(self, cmd, params, args):
return self._load_from_module(self.mod_dict, cmd.CMD_LIST)
class DodoTaskLoader(TaskLoader):
"""default task-loader create tasks from a dodo.py file"""
cmd_options = (opt_dodo, opt_cwd, opt_seek_file)
def load_tasks(self, cmd, params, args):
dodo_module = loader.get_module(params['dodoFile'], params['cwdPath'],
params['seek_file'])
return self._load_from_module(dodo_module, cmd.CMD_LIST)
class DoitCmdBase(Command):
"""
subclass must define:
cmd_options => list of option dictionary (see CmdOption)
_execute => method, argument names must be option names
"""
base_options = (opt_depfile, opt_backend)
def __init__(self, task_loader=None, dep_file=None, backend=None,
config=None, task_list=None, sel_tasks=None, outstream=None):
"""this initializer is usually just used on tests"""
# FIXME 'or TaskLoader()' below is hack for tests
self._loader = task_loader or TaskLoader()
Command.__init__(self)
# TODO: helper to test code should not be here
self.dep_class = backend_map.get(backend)
self.dep_file = dep_file # (str) filename usually '.doit.db'
self.config = config or {} # config from dodo.py & cmdline
self.task_list = task_list # list of tasks
self.sel_tasks = sel_tasks # from command line or default_tasks
self.outstream = outstream or sys.stdout
def set_options(self):
"""from base class - merge base_options, loader_options and cmd_options
"""
opt_list = (self.base_options + self._loader.cmd_options +
self.cmd_options)
return [CmdOption(opt) for opt in opt_list]
def _execute(self): # pragma: no cover
"""to be subclassed - actual command implementation"""
raise NotImplementedError
def execute(self, params, args):
"""load dodo.py, set attributes and call self._execute"""
self.task_list, self.config = self._loader.load_tasks(self, params,
args)
self.sel_tasks = args or self.config.get('default_tasks')
# check minversion
minversion = self.config.get('minversion')
if minversion:
if version_tuple(minversion) > version_tuple(doit.__version__):
msg = ('Please update doit. '
'Minimum version required is {required}. '
'You are using {actual}. ')
raise InvalidDodoFile(msg.format(required=minversion,
actual=doit.__version__))
# merge config values into params
params.update_defaults(self.config)
self.dep_file = params['dep_file']
self.dep_class = backend_map.get(params['backend'])
params['pos_args'] = args # hack
params['continue_'] = params.get('continue') # hack
# magic - create dict based on signature of _execute method
args_name = inspect.getargspec(self._execute)[0]
exec_params = dict((n, params[n]) for n in args_name if n!='self')
return self._execute(**exec_params)
# helper functions to find list of tasks
def check_tasks_exist(tasks, name_list):
"""check task exist"""
if not name_list:
return
for task_name in name_list:
if task_name not in tasks:
msg = "'%s' is not a task."
raise InvalidCommand(msg % task_name)
# this is used by commands that do not execute tasks (list, clean, forget...)
def tasks_and_deps_iter(tasks, sel_tasks, yield_duplicates=False):
"""iterator of select_tasks and its dependencies
@param tasks (dict - Task)
@param sel_tasks(list - str)
"""
processed = set() # str - task name
to_process = set(sel_tasks) # str - task name
# get initial task
while to_process:
task = tasks[to_process.pop()]
processed.add(task.name)
yield task
# FIXME this does not take calc_dep into account
for task_dep in task.task_dep + task.setup_tasks:
if (task_dep not in processed) and (task_dep not in to_process):
to_process.add(task_dep)
elif yield_duplicates:
yield tasks[task_dep]
def subtasks_iter(tasks, task):
"""find all subtasks for a given task
@param tasks (dict - Task)
@param task (Task)
"""
for name in task.task_dep:
dep = tasks[name]
if dep.is_subtask:
yield dep
|