diff options
Diffstat (limited to 'yarnlib/scenario_step_connector.py')
-rw-r--r-- | yarnlib/scenario_step_connector.py | 108 |
1 files changed, 108 insertions, 0 deletions
diff --git a/yarnlib/scenario_step_connector.py b/yarnlib/scenario_step_connector.py new file mode 100644 index 0000000..3e0712f --- /dev/null +++ b/yarnlib/scenario_step_connector.py @@ -0,0 +1,108 @@ +# Copyright 2014 Lars Wirzenius and Codethink Limited +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# =*= License: GPL-3+ =*= + + +import cliapp +import re + + +def implements_matches_step(implements, step): + '''Return re.Match if implements matches the step. + + Otherwise, return None. + + ''' + + m = re.match(implements.regexp, step.text, re.I) + if m and m.end() != len(step.text): + return None + return m + + +class StepNotImplementedError(cliapp.AppException): + + def __init__(self, scenario, step): + cliapp.AppException.__init__( + self, 'Scenario "%s", step "%s %s" has no matching ' + 'implementation' % + (scenario.name, step.what, step.text)) + + +class StepMultipleImplementationsError(cliapp.AppException): + + def __init__(self, scenario, step, matching): + s = '\n'.join( + 'IMPLEMENTS %s %s' % (i.what, i.regexp) + for i in matching) + cliapp.AppException.__init__( + self, 'Scenario "%s", step "%s %s" has more than one ' + 'matching implementations:\n%s' % + (scenario.name, step.what, step.text, s)) + + +class ScenarioStepConnector(object): + '''Connect scenario steps to their implementations.''' + + def __init__(self, implementations, missing_step_cb=lambda sc, st: None): + self.implementations = implementations + self.missing_step_cb = missing_step_cb + + def connect_implementations(self, scenarios): + '''Connect scenario steps, returning implemented scenarios. + + For each scenario given, connect known implementations to + the steps. + + This returns only scenarios that were fully implemented. + Unless ``missing_step_cb`` is provided and returns true for all + missing steps, an exception is raised instead. + + ''' + implemented_scenarios = [] + for scenario in scenarios: + missing_step = False + for step in scenario.steps: + self.connect_implementation(scenario, step) + if step.implementation is None: + missing_step = True + if not missing_step: + implemented_scenarios.append(scenario) + return implemented_scenarios + + def connect_implementation(self, scenario, step): + '''Connect the step of a scenario to its implementation. + + Always raises an exception if there are multiple matching + implementations. + + Raises an exception if there are no matching implementations + unless ``missing_step_cb`` returns true. + + ''' + matching = [i for i in self.implementations + if step.what == i.what and + implements_matches_step(i, step)] + + if len(matching) == 0: + if self.missing_step_cb(scenario, step): + return + raise StepNotImplementedError(scenario, step) + if len(matching) > 1: + raise StepMultipleImplementationsError(scenario, step, matching) + + assert step.implementation is None + step.implementation = matching[0] |