class MethodDefinition(FunctionDefinition):
    def __init__(self, class_name, comment, name, arguments, includes,
                 result_type, type_info, config, cppname=None):
        super(MethodDefinition, self).__init__(
            name, comment, arguments, includes, result_type, type_info, config, cppname)
        self.initial_args = ["%s self" % class_name]

    def _call_cpp_function(self, call_args):
        call = templates.method_call % {
            "name": self.config.call_operators.get(self.cppname, self.cppname),
            "call_args": ", ".join(call_args)}
        return catch_result(self.output_type_converter.cpp_type_decl(), call) class SetterDefinition(MethodDefinition):
    def __init__(self, python_classname, field, includes, type_info, config):
        name = "__set_%s" % field.name
        super(SetterDefinition, self).__init__(
            python_classname, "", name, [field], includes, "void", type_info,
            config)
        self.field_name = field.name

    def _call_cpp_function(self, call_args):
        assert len(call_args) == 1
        return templates.setter_call % {"name": self.field_name,
                                        "call_args": call_args[0]} class GetterDefinition(MethodDefinition):
    def __init__(self, python_classname, field, includes, type_info, config):
        name = "__get_%s" % field.name
        super(GetterDefinition, self).__init__(
            python_classname, "", name, [], includes, field.tipe, type_info,
            config)
        self.output_is_copy = False
        self.field_name = field.name

    def _call_cpp_function(self, call_args):
        assert len(call_args) == 0
        call = templates.getter_call % {"name": self.field_name}
        return catch_result(self.output_type_converter.cpp_type_decl(), call) class ConstructorDefinition(FunctionDefinition):
    def __init__(self, class_name, comment, arguments, includes, type_info,
                 config, cpp_classname):
        super(ConstructorDefinition, self).__init__(
            "__init__", comment, arguments, includes, result_type=None,
            type_info=type_info, config=config)
        self.initial_args = ["%s self" % class_name]
        self.cpp_classname = cpp_classname

    def _call_cpp_function(self, call_args):
        return templates.ctor_call % {"class_name": self.cpp_classname,
                                      "call_args": ", ".join(call_args)} class FunctionDefinition(object):
    def __init__(self, name, comment, arguments, includes, result_type,
                 type_info, config, cppname=None):
        self.name = name
        self.comment = comment
        self.arguments = arguments
        self.includes = includes
        self.initial_args = []
        self.result_type = result_type
        self.type_info = type_info
        self.config = config
        if cppname is None:
            self.cppname = self.name
        else:
            self.cppname = cppname
        self.output_is_copy = True
        self._create_type_converters()

    def _create_type_converters(self):
        skip = 0
        self.type_converters = []
        for i, arg in enumerate(self.arguments):
            if skip > 0:
                skip -= 1
                continue
            type_converter = create_type_converter(
                arg.tipe, arg.name, self.type_info, self.config,
                (self.arguments, i))
            type_converter.add_includes(self.includes)
            self.type_converters.append(type_converter)
            skip = type_converter.n_cpp_args() - 1
        self.output_type_converter = create_type_converter(
            self.result_type, None, self.type_info, self.config)
        self.output_type_converter.add_includes(self.includes)

    def make(self):
        function = self._signature()
        function["input_conversions"] = self._input_type_conversions()
        function["call"] = self._call_cpp_function(self._call_args())
        function["return_output"] = self.output_type_converter.return_output(
            self.output_is_copy)
        function["comment"] = self.comment
        return render("function", **function)

    def _signature(self):
        function_name = from_camel_case(
            self.config.cpp_to_py_operator(self.name))
        return {"def_prefix": self._def_prefix(function_name),
                "args": ", ".join(self._cython_signature_args()),
                "name": function_name}

    def _def_prefix(self, function_name):
        special_method = (function_name.startswith("__") and
                          function_name.endswith("__"))
        if special_method:
            return "def"
        else:
            return "cpdef"

    def _cython_signature_args(self):
        return self.initial_args + [tc.python_type_decl()
                                    for tc in self.type_converters]

    def _input_type_conversions(self):
        return [tc.python_to_cpp() for tc in self.type_converters]

    def _call_args(self):
        return list(chain.from_iterable(
            tc.cpp_call_args() for tc in self.type_converters))

    def _call_cpp_function(self, call_args):
        call = templates.fun_call % {"name": self.cppname,
                                     "call_args": ", ".join(call_args)}
        return catch_result(self.output_type_converter.cpp_type_decl(), call) class CythonDeclarationExporter(AstExporter):
    """Export to Cython declaration file (.pxd).

    Parameters
    ----------
    includes : Includes, optional
        Collects information about required import statements from the exporter

    config : Config, optional
        Configuration that controls e.g. template specializations
    """
    def __init__(self, includes=Includes(), config=Config()):
        super(CythonDeclarationExporter, self).__init__()
        self.includes = includes
        self.config = config

    def visit_ast(self, ast):
        self.output = render("declarations", typedefs=self.typedefs,
                             enums=self.enums, functions=self.functions,
                             classes=self.classes)

    def visit_enum(self, enum):
        self.enums.append(render("enum_decl", enum=enum))

    def visit_typedef(self, typedef):
        self.typedefs.append(templates.typedef_decl % typedef.__dict__)

    def visit_clazz(self, clazz):
        self._visit_class(clazz)

    def visit_template_class(self, template_class):
        name = "%s[%s]" % (template_class.name,
                           ", ".join(template_class.template_types))
        self._visit_class(template_class, {"name": name})

    def _visit_class(self, clazz, additional_args=None):
        if not clazz.ignored:
            class_decl = {}
            class_decl.update(clazz.__dict__)
            if additional_args is not None:
                class_decl.update(additional_args)
            class_decl["fields"] = self.fields
            class_decl["ctors"] = self.ctors
            class_decl["methods"] = self.methods
            class_decl["empty_body"] = (len(self.fields) + len(self.methods) +
                                        len(self.ctors) == 0)

            self.classes.append(render("class_decl", **class_decl))
        self._clear_class()

    def visit_field(self, field):
        if not field.ignored:
            self.fields.append(templates.field_decl % field.__dict__)

    def visit_constructor(self, ctor):
        if not ctor.ignored:
            const_dict = {"args": ", ".join(self.arguments)}
            const_dict.update(ctor.__dict__)
            const_str = templates.constructor_decl % const_dict
            self.ctors.append(const_str)
        self.arguments = []

    def visit_method(self, method):
        self._visit_method(method, templates.method_decl)

    def visit_template_method(self, template_method):
        self._visit_method(
            template_method, templates.template_method_decl,
            {"types": ", ".join(template_method.template_types)})

    def _visit_method(self, method, template, additional_args=None):
        if not method.ignored:
            method_dict = {"args": ", ".join(self.arguments)}
            method_dict.update(method.__dict__)
            if additional_args is not None:
                method_dict.update(additional_args)
            method_dict["name"] = replace_operator_decl(
                method_dict["name"], self.config)
            method_str = template % method_dict
            method_str += self._exception_suffix(method.result_type)
            self.methods.append(method_str)
        self.arguments = []

    def visit_function(self, function):
        if not function.ignored:
            function_dict = {"args": ", ".join(self.arguments)}
            function_dict.update(function.__dict__)
            function_str = templates.function_decl % function_dict
            function_str += self._exception_suffix(function.result_type)
            self.functions.append(function_str)
        self.arguments = []

    def visit_template_function(self, template_function):
        if not template_function.ignored:
            function_dict = {
                "args": ", ".join(self.arguments),
                "types": ", ".join(template_function.template_types)}
            function_dict.update(template_function.__dict__)
            function_str = templates.template_function_decl % function_dict
            function_str += self._exception_suffix(
                template_function.result_type)
            self.functions.append(function_str)
        self.arguments = []

    def visit_param(self, param):
        param_dict = param.__dict__
        param_dict["name"] = replace_keyword_argnames(param.name)
        self.arguments.append(templates.arg_decl % param_dict)

    def _exception_suffix(self, result_type):
        """Workaround for bug in Cython when returning C arrays."""
        if result_type == "char *":
            return ""
        else:
            return " except +" class Param(AstNode):
    def __init__(self, name, tipe):
        super(Param, self).__init__()
        self.name = name
        self.tipe = tipe
        self.default_value = None

    def __str__(self):
        result = "Parameter (%s) %s" % (self.tipe, self.name)
        if self.default_value is not None:
            result += " = " + str(self.default_value)
        return result class Function(FunctionBase):
    def __init__(self, filename, namespace, name, result_type, comment=""):
        super(Function, self).__init__(name, comment)
        self.filename = filename
        self.namespace = namespace
        self.result_type = result_type

    def __str__(self):
        result = super(Function, self).__str__()
        if self.result_type != "void":
            result += os.linesep + indent_block(
                "Returns (%s)" % self.result_type, 1)
        return result class Clazz(AstNode):
    def __init__(self, filename, namespace, name, comment=""):
        super(Clazz, self).__init__()
        self.filename = filename
        self.namespace = namespace
        self.name = name
        self.comment = comment
        self.base = None

    def __str__(self):
        result = "%s '%s' ('%s')" % (
            self.__class__.__name__.replace("zz", "ss"),
            self.name, self.get_cppname())
        if self.base is not None:
            result += " extends '%s'" % self.base
        if self.namespace != "":
            result += " (namespace: '%s')" % self.namespace
        if len(self.nodes) > 0:
            result += os.linesep + indent_block(os.linesep.join(
                [str(node) for node in self.nodes]), 1)
        return result

    def get_cppname(self):
        return self.name

    def get_attached_typeinfo(self):
        return {}

    def fullname(self):
        name = self.name
        if self.namespace:
            name = "%s::%s" % (self.namespace, name)
        return name class Constructor(FunctionBase):
    def __init__(self, class_name, comment=""):
        super(Constructor, self).__init__("__init__", comment)
        self.class_name = class_name class Method(FunctionBase):
    def __init__(self, name, result_type, class_name, comment=""):
        super(Method, self).__init__(name, comment)
        self.result_type = result_type
        self.class_name = class_name

    def __str__(self):
        result = super(Method, self).__str__()
        if self.result_type != "void":
            result += os.linesep + indent_block(
                "Returns (%s)" % self.result_type, 1)
        return result class Field(AstNode):
    def __init__(self, name, tipe, class_name, comment=""):
        super(Field, self).__init__()
        self.name = name
        self.tipe = tipe
        self.class_name = class_name
        self.comment = comment

    def __str__(self):
        return "Field (%s) %s" % (self.tipe, self.name) class Enum(AstNode):
    def __init__(self, filename, namespace, tipe, comment=""):
        super(Enum, self).__init__()
        self.filename = filename
        self.namespace = namespace
        self.tipe = tipe
        self.comment = comment
        self.constants = []

    def __str__(self):
        return "Enum '%s'" % self.tipe class Typedef(AstNode):
    def __init__(self, filename, namespace, tipe, underlying_type):
        super(Typedef, self).__init__()
        self.filename = filename
        self.namespace = namespace
        self.tipe = tipe
        self.underlying_type = underlying_type

    def __str__(self):
        return "Typedef (%s) %s" % (self.underlying_type, self.tipe) class Includes:
    def __init__(self):
        self.numpy = False
        self.stl = {"vector": False,
                    "string": False,
                    "deque": False,
                    "list": False,
                    "map": False,
                    "pair": False,
                    "queue": False,
                    "set": False,
                    "stack": False}
        self.deref = False

    def add_include_for(self, tname):
        for t in self.stl.keys():
            if self._part_of_tname(tname, t):
                self.stl[t] = True

    def add_include_for_deref(self):
        self.deref = True

    def add_include_for_numpy(self):
        self.numpy = True

    def _part_of_tname(self, tname, subtname):
        return (tname == subtname or tname.startswith(subtname) or
                ("<" + subtname + ">") in tname or
                ("<" + subtname + ",") in tname or
                (", " + subtname + ">") in tname or
                ("[" + subtname + "]") in tname or
                ("[" + subtname + ",") in tname or
                (", " + subtname + "]") in tname)

    def declarations_import(self):
        includes = "from libcpp cimport bool" + os.linesep

        for t in self.stl.keys():
            if self.stl[t]:
                includes += ("from libcpp.%(type)s cimport %(type)s"
                             % {"type": t}) + os.linesep

        return includes

    def implementations_import(self):
        includes = self.declarations_import()
        if self.numpy:
            includes += "cimport numpy as np" + os.linesep
            includes += "import numpy as np" + os.linesep
        if self.deref:
            includes += ("from cython.operator cimport dereference as deref" +
                         os.linesep)
        includes += "cimport _declarations as cpp" + os.linesep
        return includes class TypeInfo:
    def __init__(self, config=Config(), typedefs=None):
        self.config = config
        self.classes = []
        self.typedefs = {}
        if typedefs is not None:
            self.typedefs.update(typedefs)
        self.enums = []
        self.spec = {}

    def attach_specialization(self, spec):
        self.spec = spec

    def remove_specialization(self):
        self.spec = {}

    def underlying_type(self, tname):
        while tname in self.typedefs or tname in self.spec:
            if tname in self.typedefs:
                tname = self.typedefs[tname]
            else:
                tname = self.spec[tname]
        return tname

    def get_specialization(self, tname):
        return self.spec.get(tname, tname) def lines(*args):
    """Make multi-line string."""
    return os.linesep.join(args) class Config(object):
    def __init__(self):
        # file endings
        self.cpp_header_endings = ["h", "hh", "hpp"]
        self.python_file_ending = "py"
        self.pyx_file_ending = "pyx"
        self.pxd_file_ending = "pxd"

        # Operator mapping
        # overview of Cython operators:
        # http://docs.cython.org/src/reference/special_methods_table.html
        # overview of C++ operators:
        # http://en.cppreference.com/w/cpp/language/operators
        self.operators = {
            "operator()": "__call__",
            "operator[]": "__getitem__",
            "operator+": "__add__",
            "operator-": "__sub__",
            "operator*": "__mul__",
            "operator/": "__div__",
            "operator%": "__mod__",
            "operator&&": "__and__",
            "operator&": "__and__",
            "operator||": "__or__",
            "operator|": "__or__",
            "operator+=": "__iadd__",
            "operator-=": "__isub__",
            "operator*=": "__imul__",
            "operator/=": "__idiv__",
            "operator%=": "__imod__",
            "operator&=": "__iand__",
            "operator|=": "__ior__",
        }
        self.call_operators = {
            "operator()": "call",
            "operator[]": "getitem",
            "operator+": "add",
            "operator-": "sub",
            "operator*": "mul",
            "operator/": "div",
            "operator%": "mod",
            "operator&&": "opand",
            "operator&": "opand",
            "operator||": "opor",
            "operator|": "opor",
            "operator+=": "iadd",
            "operator-=": "isub",
            "operator*=": "imul",
            "operator/=": "idiv",
            "operator%=": "imod",
            "operator&=": "iand",
            "operator|=": "ior",
        }

        self.registered_converters = []
        self.registered_template_specializations = {}
        self.additional_declerations = []
        self.ignored = []

        self.library_dirs = []
        self.libraries = []

    def cpp_to_py_operator(self, name):
        if name.startswith("operator") and name not in self.operators:
            raise NotImplementedError("Cannot convert C++ operator '%s' to "
                                      "Python operator." % name)
        return self.operators.get(name, name)

    def add_decleration(self, decl):
        self.additional_declerations.append(decl)

    def register_class_specialization(self, cpp_classname, python_classname,
                                      template_to_type):
        self._register_specialization(cpp_classname, python_classname,
                                      template_to_type)

    def register_function_specialization(self, cpp_functionname,
                                         python_classname, template_to_type):
        self._register_specialization(cpp_functionname, python_classname,
                                      template_to_type)

    def register_method_specialization(self, cpp_classname, cpp_methodname,
                                       python_methodname, template_to_type):
        self._register_specialization(cpp_classname + "::" + cpp_methodname,
                                      python_methodname, template_to_type)

    def _register_specialization(self, key, name, template_to_type):
        if key not in self.registered_template_specializations:
            self.registered_template_specializations[key] = []
        self.registered_template_specializations[key].append(
            (name, template_to_type))

    def ignore_class(self, filename, class_name):
        self.ignore(filename, class_name)

    def is_ignored_class(self, filename, class_name):
        return self.is_ignored(filename, class_name)

    def ignore_method(self, class_name, method_name):
        self.ignore(class_name, method_name)

    def is_ignored_method(self, class_name, method_name):
        return self.is_ignored(class_name, method_name)

    def ignore(self, *args):
        self.ignored.append(":".join(args))

    def is_ignored(self, *args):
        return ":".join(args) in self.ignored

    def abstract_class(self, class_name):
        self.ignore(class_name, "__init__")

    def is_abstract_class(self, class_name):
        return self.is_ignored(class_name, "__init__")

    def add_library_dir(self, library_dir):
        self.library_dirs.append(library_dir)

    def add_library(self, library):
        self.libraries.append(library)
from pywrap.exporter import (MethodDefinition, SetterDefinition,
                             GetterDefinition, ConstructorDefinition,
                             FunctionDefinition, CythonDeclarationExporter)
from pywrap.ast import (Param, Function, Clazz, Constructor, Method,
                        Field, Enum, Typedef)
from pywrap.parser import Includes, TypeInfo
from pywrap.utils import lines
from pywrap.defaultconfig import Config
from nose.tools import assert_multi_line_equal
    field = Field("myField", "double", "MyClass")
    setter = SetterDefinition(
        "MyClass", field, Includes(), TypeInfo(), Config()).make()
    assert_multi_line_equal(
        setter,
        lines(
            "cpdef __set_my_field(MyClass self, double myField):",
            "    cdef double cpp_myField = myField",
            "    self.thisptr.myField = cpp_myField"
        )
    )


def test_getter_definition():
    field = Field("myField", "double", "MyClass")
    getter = GetterDefinition(
        "MyClass", field, Includes(), TypeInfo(), Config()).make()
    assert_multi_line_equal(
        getter,
        lines(
            "cpdef __get_my_field(MyClass self):",
            "    cdef double result = self.thisptr.myField",
            "    return result",
            ""
        )
    )

def test_default_ctor_def():
    ctor = ConstructorDefinition("MyClass", "", [], Includes(), TypeInfo(),
                                 Config(), "MyClass").make()
    assert_multi_line_equal(
        ctor,
        lines(
            "def __init__(MyClass self):",
            "    self.thisptr = new cpp.MyClass()"
        )
    )


def test_function_def():
    fun = FunctionDefinition("myFun", "", [], Includes(), "void", TypeInfo(),
                             Config()).make()
    assert_multi_line_equal(
        fun,
        lines(
            "cpdef my_fun():",
            "    cpp.myFun()"
        )
    )


def test_function_def_with_another_cppname():
    fun = FunctionDefinition("myFunInt", "", [], Includes(), "void", TypeInfo(),
                             Config(), cppname="myFun").make()
    assert_multi_line_equal(
        fun,
        lines(
            "cpdef my_fun_int():",
            "    cpp.myFun()"
        )
    )


def test_function_decl():
    fun = Function("test.hpp", "", "myFun", "void")
    ignored_fun = Function("test.hpp", "", "myFun", "void")
    ignored_fun.ignored = True
    exporter = CythonDeclarationExporter(Includes(), Config())
    exporter.visit_function(fun)
    exporter.visit_function(ignored_fun)
    exporter.visit_ast(None)
    decl = exporter.export()
    assert_multi_line_equal(
        decl.strip(),
        lines(
            "cdef extern from \"test.hpp\" namespace \"\":",
            "    void myFun() except +"
        )
    )


def test_class_decl():
    clazz = Clazz("test.hpp", "", "MyClass")
    exporter = CythonDeclarationExporter(Includes(), Config())
    exporter.visit_clazz(clazz)
    exporter.visit_ast(None)
    decl = exporter.export()
    assert_multi_line_equal(
        decl.strip(),
        lines(
            "cdef extern from \"test.hpp\" namespace \"\":",
            "    cdef cppclass MyClass:",
            "        pass"
        )
    )


def test_ctor_decl():
    clazz = Clazz("test.hpp", "", "MyClass")
    ctor = Constructor("MyClass")
    ignored_ctor = Constructor("MyClass")
    ignored_ctor.ignored = True
    exporter = CythonDeclarationExporter(Includes(), Config())
    exporter.visit_constructor(ctor)
    exporter.visit_constructor(ignored_ctor)
    exporter.visit_clazz(clazz)
    exporter.visit_ast(None)
    decl = exporter.export()
    assert_multi_line_equal(
        decl.strip(),
        lines(
            "cdef extern from \"test.hpp\" namespace \"\":",
            "    cdef cppclass MyClass:",
            "        MyClass()"
        )
    )


def test_method_decl():
    clazz = Clazz("test.hpp", "", "MyClass")

    method = Method("myMethod", "void", "MyClass")