Skip to content

Commit

Permalink
Add feature to specify task parameters in doit.cfg (issue #238)
Browse files Browse the repository at this point in the history
  • Loading branch information
rbdixon authored and schettino72 committed Sep 10, 2019
1 parent 660d3a2 commit f0303f2
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 40 deletions.
11 changes: 11 additions & 0 deletions doc/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,17 @@ Check the :ref:`plugins <plugins>` section for an introduction
on available plugin categories.


per-task sections
^^^^^^^^^^^^^^^^^

To configure options for a specific task, use a section with
the task name prefixed with "task:"::

[task:make_cookies]
cookie_type = chocolate
temp = 375F
duration = 12

configuration at *dodo.py*
--------------------------

Expand Down
10 changes: 9 additions & 1 deletion doit/cmd_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -384,9 +384,17 @@ def load_doit_config(self):
return loader.load_doit_config(self.namespace)

def load_tasks(self, cmd, pos_args):
return loader.load_tasks(self.namespace, self.cmd_names,
tasks = loader.load_tasks(self.namespace, self.cmd_names,
cmd.execute_tasks)

# Add task options from config, if present
if self.config is not None:
for task in tasks:
task_stanza = 'task:' + task.name
if task_stanza in self.config:
task.cfg_values = self.config[task_stanza]

return tasks

class ModuleTaskLoader(NamespaceTaskLoader):
"""load tasks from a module/dictionary containing task generators
Expand Down
8 changes: 4 additions & 4 deletions doit/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import re

from .exceptions import InvalidTask, InvalidCommand, InvalidDodoFile
from .cmdparse import TaskParse, CmdOption
from .task import Task, DelayedLoaded
from .loader import generate_tasks

Expand Down Expand Up @@ -156,9 +155,10 @@ def add_filtered_task(seq, f_name):
if f_name in self.tasks:
# parse task_selection
the_task = self.tasks[f_name]
# remaining items are other tasks not positional options
taskcmd = TaskParse([CmdOption(opt) for opt in the_task.params])
the_task.options, seq = taskcmd.parse(seq)

# Initialize options for the task
seq = the_task.init_options(seq)

# if task takes positional parameters set all as pos_arg_val
if the_task.pos_arg is not None:
the_task.pos_arg_val = seq
Expand Down
24 changes: 18 additions & 6 deletions doit/task.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ class Task(object):
'getargs': ((dict,), ()),
'title': ((Callable,), (None,)),
'watch': ((list, tuple), ()),
}
}


def __init__(self, name, actions, file_dep=(), targets=(),
Expand Down Expand Up @@ -230,6 +230,7 @@ def __init__(self, name, actions, file_dep=(), targets=(),
self.values = {}
self.verbosity = verbosity
self.custom_title = title
self.cfg_values = None

# clean
if clean is True:
Expand Down Expand Up @@ -358,17 +359,28 @@ def update_deps(self, deps):
self._expand_map[dep](self, dep_values)


def init_options(self):
def init_options(self, args=None):
"""Put default values on options.
This will only be used, if params options were not passed
on the command line.
This function will only initialize task options once. If provided the args
parameter will be parsed for command line arguments intended for this task.
Return value: unparsed command line task arguments or None.
"""
if self.options is None:
self.options = {}
taskcmd = TaskParse([CmdOption(opt) for opt in self.params])
# ignore positional parameters
self.options = taskcmd.parse('')[0]
if self.cfg_values is not None:
taskcmd.overwrite_defaults(self.cfg_values)

if args is None:
# ignore positional parameters
self.options.update(taskcmd.parse('')[0])
return None
else:
parsed_options, args = taskcmd.parse(args)
self.options.update(parsed_options)
return args

def _init_getargs(self):
"""task getargs attribute define implicit task dependencies"""
Expand Down
19 changes: 19 additions & 0 deletions tests/test_cmd_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,25 @@ def test_load_tasks_from_module(self):
assert ['xxx1'] == [t.name for t in task_list]
assert {'verbose': 2} == config

def test_task_config(self):
'Ensure that doit.cfg specified task parameters are applied.'

cmd = Command()
members = {'task_foo': lambda: {'actions':[],
'params': [{
'name': 'x',
'default': None,
'long': 'x'
}]},
'DOIT_CONFIG': {'task:foo': {'x': 1}},
}
loader = ModuleTaskLoader(members)
loader.setup({})
loader.config = loader.load_doit_config()
task_list = loader.load_tasks(cmd, [])
task = task_list.pop()
task.init_options()
assert 1 == task.options['x']

class TestDodoTaskLoader(object):
def test_load_tasks(self, restore_cwd):
Expand Down
59 changes: 30 additions & 29 deletions tests/test_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,44 +59,45 @@ def test_bug770150_task_dependency_from_target(self):
TaskControl([t1, t2, t3])
assert ['taskZ', 'taskX'] == t2.task_dep


TASKS_SAMPLE = [Task("t1", [""], doc="t1 doc string"),
Task("t2", [""], doc="t2 doc string"),
Task("g1", None, doc="g1 doc string"),
Task("g1.a", [""], doc="g1.a doc string", subtask_of='g1'),
Task("g1.b", [""], doc="g1.b doc string", subtask_of='g1'),
Task("t3", [""], doc="t3 doc string",
params=[{'name':'opt1','long':'message','default':''}])]

@pytest.fixture
def tasks_sample():
return [Task("t1", [""], doc="t1 doc string"),
Task("t2", [""], doc="t2 doc string"),
Task("g1", None, doc="g1 doc string"),
Task("g1.a", [""], doc="g1.a doc string", subtask_of='g1'),
Task("g1.b", [""], doc="g1.b doc string", subtask_of='g1'),
Task("t3", [""], doc="t3 doc string",
params=[{'name':'opt1','long':'message','default':''}])
]

class TestTaskControlCmdOptions(object):
def testFilter(self):
def testFilter(self, tasks_sample):
filter_ = ['t2', 't3']
tc = TaskControl(TASKS_SAMPLE)
tc = TaskControl(tasks_sample)
assert filter_ == tc._filter_tasks(filter_)

def testProcessSelection(self):
def testProcessSelection(self, tasks_sample):
filter_ = ['t2', 't3']
tc = TaskControl(TASKS_SAMPLE)
tc = TaskControl(tasks_sample)
tc.process(filter_)
assert filter_ == tc.selected_tasks

def testProcessAll(self):
tc = TaskControl(TASKS_SAMPLE)
def testProcessAll(self, tasks_sample):
tc = TaskControl(tasks_sample)
tc.process(None)
assert ['t1', 't2', 'g1', 'g1.a', 'g1.b', 't3'] == tc.selected_tasks

def testFilterPattern(self):
tc = TaskControl(TASKS_SAMPLE)
def testFilterPattern(self, tasks_sample):
tc = TaskControl(tasks_sample)
assert ['t1', 'g1', 'g1.a', 'g1.b'] == tc._filter_tasks(['*1*'])

def testFilterSubtask(self):
def testFilterSubtask(self, tasks_sample):
filter_ = ["t1", "g1.b"]
tc = TaskControl(TASKS_SAMPLE)
tc = TaskControl(tasks_sample)
assert filter_ == tc._filter_tasks(filter_)

def testFilterTarget(self):
tasks = list(TASKS_SAMPLE)
def testFilterTarget(self, tasks_sample):
tasks = list(tasks_sample)
tasks.append(Task("tX", [""],[],["targetX"]))
tc = TaskControl(tasks)
assert ['tX'] == tc._filter_tasks(["targetX"])
Expand Down Expand Up @@ -184,8 +185,8 @@ def test_filter_delayed_regex_auto(self):


# filter a non-existent task raises an error
def testFilterWrongName(self):
tc = TaskControl(TASKS_SAMPLE)
def testFilterWrongName(self, tasks_sample):
tc = TaskControl(tasks_sample)
pytest.raises(InvalidCommand, tc._filter_tasks, ['no'])

def testFilterWrongSubtaskName(self):
Expand All @@ -194,19 +195,19 @@ def testFilterWrongSubtaskName(self):
tc = TaskControl([t1, t2])
pytest.raises(InvalidCommand, tc._filter_tasks, ['taskX:no'])

def testFilterEmptyList(self):
def testFilterEmptyList(self, tasks_sample):
filter_ = []
tc = TaskControl(TASKS_SAMPLE)
tc = TaskControl(tasks_sample)
assert filter_ == tc._filter_tasks(filter_)

def testOptions(self):
def testOptions(self, tasks_sample):
options = ["t3", "--message", "hello option!", "t1"]
tc = TaskControl(TASKS_SAMPLE)
tc = TaskControl(tasks_sample)
assert ['t3', 't1'] == tc._filter_tasks(options)
assert "hello option!" == tc.tasks['t3'].options['opt1']

def testPosParam(self):
tasks = list(TASKS_SAMPLE)
def testPosParam(self, tasks_sample):
tasks = list(tasks_sample)
tasks.append(Task("tP", [""],[],[], pos_arg='myp'))
tc = TaskControl(tasks)
args = ["tP", "hello option!", "t1"]
Expand Down
23 changes: 23 additions & 0 deletions tests/test_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,29 @@ def test_options(self):
assert 'pos' == t.pos_arg
assert None == t.pos_arg_val # always uninitialized

def test_options_from_cfg(self):
'Ensure that doit.cfg can specify task options.'
p1 = {'name': 'x', 'long': 'x', 'default': None}
t = task.Task("MyName", None, params=[p1])
t.cfg_values = {'x': 1}
assert t.options is None
t.init_options()
assert t.options is not None
assert 1 == t.options['x']

def test_options_from_cfg_override(self):
'Ensure that doit.cfg specified task options can be replaced by command line specified options.'

p1 = {'name': 'x', 'long': 'x', 'default': None, 'type': int}
p2 = {'name': 'y', 'long': 'y', 'default': 2, 'type': int}
t = task.Task("MyName", None, params=[p1, p2])
t.cfg_values = {'x': 1}
assert t.options is None
t.init_options(['--x=2'])
assert t.options is not None
assert 2 == t.options['x']
assert 2 == t.options['y']

def test_setup(self):
t = task.Task("task5", ['action'], setup=["task2"])
assert ["task2"] == t.setup_tasks
Expand Down

0 comments on commit f0303f2

Please sign in to comment.