diff --git a/doit/action.py b/doit/action.py index e7b0e2fa..f4d99353 100644 --- a/doit/action.py +++ b/doit/action.py @@ -7,6 +7,7 @@ import inspect from pathlib import PurePath from threading import Thread +from typing import Union import pdb from .exceptions import InvalidTask, TaskFailed, TaskError @@ -27,6 +28,9 @@ class BaseAction(object): # must implement: # def execute(self, out=None, err=None) + # must implement: + # def title(self) + @staticmethod def _prepare_kwargs(task, func, args, kwargs): """ @@ -153,6 +157,15 @@ def action(self): kwargs = self._prepare_kwargs(self.task, ref, args, kw) return ref(*args, **kwargs) + def title(self) -> Union[str, TaskError]: + try: + action = self.expand_action() + except Exception as exc: + return TaskError( + "CmdAction Error creating command string", exc) + assert isinstance(action, str) + return action + def _print_process_output(self, process, input_, capture, realtime): """Reads 'input_' until process is terminated. @@ -396,6 +409,9 @@ def __init__(self, py_callable, args=None, kwargs=None, task=None): msg = "%r kwargs must be a 'dict'. got '%s'" raise InvalidTask(msg % (self.task, self.kwargs)) + def title(self) -> str: + return f"{self.py_callable.__name__}(*{self.args}, **{self._prepare_kwargs()})" + def _prepare_kwargs(self): return BaseAction._prepare_kwargs(self.task, self.py_callable, diff --git a/doit/reporter.py b/doit/reporter.py index 6ac59f69..c723aa29 100644 --- a/doit/reporter.py +++ b/doit/reporter.py @@ -21,6 +21,7 @@ def __init__(self, outstream, options): self.failures = [] self.runtime_errors = [] self.failure_verbosity = options.get('failure_verbosity', 0) + self.show_action = options.get('show_action', False) self.outstream = outstream def write(self, text): @@ -42,6 +43,11 @@ def execute_task(self, task): if task.actions and (task.name[0] != '_'): self.write('. %s\n' % task.title()) + def execute_action(self, action): + """called before executing each action""" + if self.show_action: + self.write(' + %s\n' % action.title()) + def add_failure(self, task, exception): """called when execution finishes with a failure""" result = {'task': task, 'exception':exception} @@ -137,7 +143,7 @@ def _just_pass(self, *args): """over-write base to do nothing""" pass - get_status = execute_task = add_failure = add_success \ + get_status = execute_task = execute_action = add_failure = add_success \ = skip_uptodate = skip_ignore = teardown_task = complete_run \ = _just_pass diff --git a/doit/runner.py b/doit/runner.py index 572058f2..662ac988 100644 --- a/doit/runner.py +++ b/doit/runner.py @@ -174,7 +174,7 @@ def execute_task(self, task): # finally execute it! self.reporter.execute_task(task) - return task.execute(self.stream) + return task.execute(self.stream, self.reporter) def process_task_result(self, node, catched_excp): @@ -227,7 +227,7 @@ def teardown(self): """run teardown from all tasks""" for task in reversed(self.teardown_list): self.reporter.teardown_task(task) - catched = task.execute_teardown(self.stream) + catched = task.execute_teardown(self.stream, self.reporter) if catched: msg = "ERROR: task '%s' teardown action" % task.name error = SetupError(msg, catched) diff --git a/doit/task.py b/doit/task.py index 2ad04f83..d9e7b9a5 100644 --- a/doit/task.py +++ b/doit/task.py @@ -10,8 +10,9 @@ from .cmdparse import CmdOption, TaskParse from .exceptions import CatchedException, InvalidTask -from .action import create_action, PythonAction +from .action import create_action, BaseAction, PythonAction from .dependency import UptodateCalculator +from .reporter import ConsoleReporter def first_line(doc): @@ -449,7 +450,7 @@ def save_extra_values(self): def overwrite_verbosity(self, stream): self.verbosity = stream.effective_verbosity(self.verbosity) - def execute(self, stream): + def execute(self, stream, reporter=None): """Executes the task. @return failure: see CmdAction.execute """ @@ -457,6 +458,8 @@ def execute(self, stream): self.init_options() task_stdout, task_stderr = stream._get_out_err(self.verbosity) for action in self.actions: + if isinstance(action, BaseAction) and isinstance(reporter, ConsoleReporter): + reporter.execute_action(action) action_return = action.execute(task_stdout, task_stderr) if isinstance(action_return, CatchedException): return action_return @@ -464,12 +467,14 @@ def execute(self, stream): self.values.update(action.values) - def execute_teardown(self, stream): + def execute_teardown(self, stream, reporter=None): """Executes task's teardown @return failure: see CmdAction.execute """ task_stdout, task_stderr = stream._get_out_err(self.verbosity) for action in self.teardown: + if isinstance(action, BaseAction) and isinstance(reporter, ConsoleReporter): + reporter.execute_action(action) action_return = action.execute(task_stdout, task_stderr) if isinstance(action_return, CatchedException): return action_return diff --git a/tests/test_action.py b/tests/test_action.py index fdbfc191..7a5fcc97 100644 --- a/tests/test_action.py +++ b/tests/test_action.py @@ -85,6 +85,11 @@ def test_values(self): my_action.execute() assert {} == my_action.values + def test_title(self): + cmd = "%s 1 2" % PROGRAM + my_action = action.CmdAction(cmd) + assert cmd == my_action.title() + class TestCmdActionParams(object): def test_invalid_param_stdout(self): @@ -596,6 +601,11 @@ def vvv(): return {'x': 5, 'y':10} my_action.execute() assert {'x': 5, 'y':10} == my_action.values + def test_title(self): + my_action = action.PythonAction(self._func_par,args=(2,2), + kwargs={'par3':25}) + assert "_func_par(*(2, 2), **{'par3': 25})" == my_action.title() + class TestPythonVerbosity(object): def write_stderr(self): diff --git a/tests/test_reporter.py b/tests/test_reporter.py index 85ae3296..4f20625b 100644 --- a/tests/test_reporter.py +++ b/tests/test_reporter.py @@ -3,9 +3,12 @@ from io import StringIO from doit import reporter +from doit.action import BaseAction from doit.task import Stream, Task from doit.exceptions import CatchedException +import pytest + class TestConsoleReporter(object): @@ -28,6 +31,21 @@ def do_nothing():pass rep.execute_task(t1) assert ". with_action\n" == rep.outstream.getvalue() + @pytest.mark.parametrize( + "options,expected", + [ + ({}, ""), + ({"show_action": True}, " + action_title\n"), + ({"show_action": False}, ""), + ], + ) + def test_executeAction(self, options, expected): + rep = reporter.ConsoleReporter(StringIO(), options) + a1 = BaseAction() + a1.title = lambda: "action_title" + rep.execute_action(a1) + assert expected == rep.outstream.getvalue() + def test_executeTask_unicode(self): rep = reporter.ConsoleReporter(StringIO(), {}) def do_nothing():pass diff --git a/tests/test_task.py b/tests/test_task.py index 678b1bba..00ab8d78 100644 --- a/tests/test_task.py +++ b/tests/test_task.py @@ -10,6 +10,7 @@ from doit.exceptions import TaskError from doit.exceptions import CatchedException from doit import action +from doit import reporter from doit import task from doit.task import Stream @@ -392,6 +393,26 @@ def my_raise(): assert isinstance(got, CatchedException) +def py_fn(x): + pass + +@pytest.mark.parametrize( + "t", + [ + task.Task("t1", [(py_fn, [1]), (py_fn, [2])]), + task.Task("t1", [], teardown=[(py_fn, [1]), (py_fn, [2])]), + ], +) +@pytest.mark.parametrize( + "show_action,expected", + [(False, ""), (True, " + py_fn(*[1], **{})\n + py_fn(*[2], **{})\n")], +) +def test_show_action(t, show_action, expected): + rep = reporter.ConsoleReporter(StringIO(), {"show_action": show_action}) + t.execute(Stream(0), rep) + t.execute_teardown(Stream(0), rep) + assert expected == rep.outstream.getvalue() + class TestTaskClean(object): @pytest.fixture