# Path: nimoy/ast_tools/features.py
# class FeatureRegistrationTransformer(ast.NodeTransformer):
#     def __init__(self, runner_context: RunnerContext, spec_location, spec_metadata: SpecMetadata) -> None:
#         super().__init__()
#         self.runner_context = runner_context
#         self.spec_location = spec_location
#         self.spec_metadata = spec_metadata
# 
#     def visit_FunctionDef(self, feature_node):
#         if FeatureRegistrationTransformer._skip_feature(feature_node):
#             return feature_node
# 
#         feature_name = feature_node.name
#         if not feature_name.startswith('_'):
# 
#             feature_name_specified = hasattr(self.spec_location, 'feature_name')
# 
#             if not feature_name_specified or (
#                     feature_name_specified and self.spec_location.feature_name == feature_name):
#                 self.spec_metadata.add_feature(feature_name)
#                 FeatureBlockTransformer(self.runner_context, self.spec_metadata, feature_name).visit(feature_node)
#                 FeatureBlockRuleEnforcer(self.spec_metadata, feature_name, feature_node).enforce_tail_end_rules()
# 
#         feature_variables = self.spec_metadata.feature_variables.get(feature_name)
#         if feature_variables:
#             existing_arg_names = [existing_arg.arg for existing_arg in feature_node.args.args]
# 
#             for feature_variable in feature_variables:
#                 if feature_variable in existing_arg_names:
#                     continue
#                 feature_node.args.args.append(_ast.arg(arg=feature_variable))
#                 feature_node.args.defaults.append(ast.NameConstant(value=None))
# 
#         if self._feature_has_a_where_function(feature_name):
#             self._remove_where_function_from_node(feature_name, feature_node)
#         return feature_node
# 
#     @staticmethod
#     def _remove_where_function_from_node(feature_name, feature_node):
#         where_function = FeatureRegistrationTransformer._locate_where_function_within_feature(feature_name,
#                                                                                               feature_node)
#         feature_node.body.remove(where_function)
# 
#     def _feature_has_a_where_function(self, feature_name):
#         return self.spec_metadata.where_functions.get(feature_name)
# 
#     @staticmethod
#     def _locate_where_function_within_feature(feature_name, feature_node):
#         def _is_a_where_function(body_element):
#             return hasattr(body_element, 'name') and body_element.name == feature_name + '_where'
# 
#         return next(body_element for body_element in feature_node.body if _is_a_where_function(body_element))
# 
#     @staticmethod
#     def _skip_feature(feature_node):
#         decorators = feature_node.decorator_list
#         return any((hasattr(decorator, 'attr') and decorator.attr == 'skip') for decorator in decorators)
# 
# Path: nimoy/runner/metadata.py
# class RunnerContext:
# 
#     def __init__(self, use_power_assertions: bool = False):
#         self.use_power_assertions = use_power_assertions
# 
# Path: nimoy/runner/spec_finder.py
# class Location:
#     def __init__(self, suggested_location):
# 
#         # Format may be:
#         # - some_spec.py
#         # - some_spec.py::SpecName
#         # - some_spec.py::feature_name
#         # - some_spec.py::SpecName::feature_name
#         split_suggested_location = suggested_location.split("::")
# 
#         # some_spec.py
#         if len(split_suggested_location) == 1:
#             self.spec_path = split_suggested_location[0]
# 
#         # some_spec.py::SpecName or some_spec.py::feature_name
#         if len(split_suggested_location) == 2:
#             self.spec_path = split_suggested_location[0]
# 
#             # some_spec.py::SpecName
#             if split_suggested_location[1][0].isupper():
#                 self.spec_name = split_suggested_location[1]
# 
#             # some_spec.py::feature_name
#             else:
#                 self.feature_name = split_suggested_location[1]
# 
#         # some_spec.py::SpecName::feature_name
#         if len(split_suggested_location) == 3:
#             self.spec_path = split_suggested_location[0]
#             self.spec_name = split_suggested_location[1]
#             self.feature_name = split_suggested_location[2]
# 
# Path: nimoy/specification.py
# class Specification(TestCase, metaclass=DataDrivenSpecification):
# 
#     def __init__(self, methodName='runTest'):
#         super().__init__(methodName)
#         self.thrown_exceptions = []
# 
#     def _feature_block_context(self, block_name):
#         return FeatureBlock(block_name, self.thrown_exceptions)
# 
#     def _compare(self, left, right, comparison_type_name):
#         Compare().compare(left, right, comparison_type_name)
# 
#     def _power_assert(self, expression: Dict):
#         PowerAssertions().assert_and_raise(expression)
# 
#     def _assert_mock(self, number_of_invocations, mock, method, *args):
#         MockAssertions().assert_mock(number_of_invocations, mock, method, *args)
# 
#     def _exception_thrown(self, expected_exception_type):
#         return ExceptionAssertions().assert_exception(self.thrown_exceptions, expected_exception_type)


import ast
from unittest import mock
from nimoy.ast_tools.ast_metadata import SpecMetadata
from nimoy.ast_tools.features import FeatureRegistrationTransformer
from nimoy.runner.metadata import RunnerContext
from nimoy.runner.spec_finder import Location
from nimoy.specification import Specification



class FeatureRegistrationTransformerSpec(Specification):

    @mock.patch('nimoy.ast_tools.features.FeatureBlockRuleEnforcer')
    @mock.patch('nimoy.ast_tools.features.FeatureBlockTransformer')
    def a_feature_was_added(self, feature_block_transformer, feature_block_rule_enforcer):
        with setup:

# Path: nimoy/ast_tools/features.py
# class FeatureRegistrationTransformer(ast.NodeTransformer):
#     def __init__(self, runner_context: RunnerContext, spec_location, spec_metadata: SpecMetadata) -> None:
#         super().__init__()
#         self.runner_context = runner_context
#         self.spec_location = spec_location
#         self.spec_metadata = spec_metadata
# 
#     def visit_FunctionDef(self, feature_node):
#         if FeatureRegistrationTransformer._skip_feature(feature_node):
#             return feature_node
# 
#         feature_name = feature_node.name
#         if not feature_name.startswith('_'):
# 
#             feature_name_specified = hasattr(self.spec_location, 'feature_name')
# 
#             if not feature_name_specified or (
#                     feature_name_specified and self.spec_location.feature_name == feature_name):
#                 self.spec_metadata.add_feature(feature_name)
#                 FeatureBlockTransformer(self.runner_context, self.spec_metadata, feature_name).visit(feature_node)
#                 FeatureBlockRuleEnforcer(self.spec_metadata, feature_name, feature_node).enforce_tail_end_rules()
# 
#         feature_variables = self.spec_metadata.feature_variables.get(feature_name)
#         if feature_variables:
#             existing_arg_names = [existing_arg.arg for existing_arg in feature_node.args.args]
# 
#             for feature_variable in feature_variables:
#                 if feature_variable in existing_arg_names:
#                     continue
#                 feature_node.args.args.append(_ast.arg(arg=feature_variable))
#                 feature_node.args.defaults.append(ast.NameConstant(value=None))
# 
#         if self._feature_has_a_where_function(feature_name):
#             self._remove_where_function_from_node(feature_name, feature_node)
#         return feature_node
# 
#     @staticmethod
#     def _remove_where_function_from_node(feature_name, feature_node):
#         where_function = FeatureRegistrationTransformer._locate_where_function_within_feature(feature_name,
#                                                                                               feature_node)
#         feature_node.body.remove(where_function)
# 
#     def _feature_has_a_where_function(self, feature_name):
#         return self.spec_metadata.where_functions.get(feature_name)
# 
#     @staticmethod
#     def _locate_where_function_within_feature(feature_name, feature_node):
#         def _is_a_where_function(body_element):
#             return hasattr(body_element, 'name') and body_element.name == feature_name + '_where'
# 
#         return next(body_element for body_element in feature_node.body if _is_a_where_function(body_element))
# 
#     @staticmethod
#     def _skip_feature(feature_node):
#         decorators = feature_node.decorator_list
#         return any((hasattr(decorator, 'attr') and decorator.attr == 'skip') for decorator in decorators)
# 
# Path: nimoy/runner/metadata.py
# class RunnerContext:
# 
#     def __init__(self, use_power_assertions: bool = False):
#         self.use_power_assertions = use_power_assertions
# 
# Path: nimoy/runner/spec_finder.py
# class Location:
#     def __init__(self, suggested_location):
# 
#         # Format may be:
#         # - some_spec.py
#         # - some_spec.py::SpecName
#         # - some_spec.py::feature_name
#         # - some_spec.py::SpecName::feature_name
#         split_suggested_location = suggested_location.split("::")
# 
#         # some_spec.py
#         if len(split_suggested_location) == 1:
#             self.spec_path = split_suggested_location[0]
# 
#         # some_spec.py::SpecName or some_spec.py::feature_name
#         if len(split_suggested_location) == 2:
#             self.spec_path = split_suggested_location[0]
# 
#             # some_spec.py::SpecName
#             if split_suggested_location[1][0].isupper():
#                 self.spec_name = split_suggested_location[1]
# 
#             # some_spec.py::feature_name
#             else:
#                 self.feature_name = split_suggested_location[1]
# 
#         # some_spec.py::SpecName::feature_name
#         if len(split_suggested_location) == 3:
#             self.spec_path = split_suggested_location[0]
#             self.spec_name = split_suggested_location[1]
#             self.feature_name = split_suggested_location[2]
# 
# Path: nimoy/specification.py
# class Specification(TestCase, metaclass=DataDrivenSpecification):
# 
#     def __init__(self, methodName='runTest'):
#         super().__init__(methodName)
#         self.thrown_exceptions = []
# 
#     def _feature_block_context(self, block_name):
#         return FeatureBlock(block_name, self.thrown_exceptions)
# 
#     def _compare(self, left, right, comparison_type_name):
#         Compare().compare(left, right, comparison_type_name)
# 
#     def _power_assert(self, expression: Dict):
#         PowerAssertions().assert_and_raise(expression)
# 
#     def _assert_mock(self, number_of_invocations, mock, method, *args):
#         MockAssertions().assert_mock(number_of_invocations, mock, method, *args)
# 
#     def _exception_thrown(self, expected_exception_type):
#         return ExceptionAssertions().assert_exception(self.thrown_exceptions, expected_exception_type)


# Path: specs/nimoy/ast_tools/features_spec.py
import ast
from unittest import mock
from nimoy.ast_tools.ast_metadata import SpecMetadata
from nimoy.ast_tools.features import FeatureRegistrationTransformer
from nimoy.runner.metadata import RunnerContext
from nimoy.runner.spec_finder import Location
from nimoy.specification import Specification




class FeatureRegistrationTransformerSpec(Specification):

    @mock.patch('nimoy.ast_tools.features.FeatureBlockRuleEnforcer')
    @mock.patch('nimoy.ast_tools.features.FeatureBlockTransformer')
    def a_feature_was_added(self, feature_block_transformer, feature_block_rule_enforcer):
        with setup:

            runner_context = RunnerContext()