module PyCall module Conversions @python_type_map = [] class TypePair < Struct.new(:pytype, :rbtype) def to_a [pytype, rbtype] end end def self.each_type_pair i, n = 1, @python_type_map.length while i <= n yield @python_type_map[n - i] i += 1 end self end def self.python_type_mapping(pytype, rbtype) each_type_pair do |type_pair| next unless pytype == type_pair.pytype type_pair.rbtype = rbtype return end @python_type_map << TypePair.new(pytype, rbtype) end # Convert a PyCall::PyObjectStruct object to a Ruby object # # @param [PyCall::PyObjectStruct] pyptr a PyObjectStruct object. # # @return a Ruby object converted from `pyptr`. def self.to_ruby(pyptr) return nil if pyptr.null? || PyCall.none?(pyptr) case when PyCall::Types.pyisinstance(pyptr, LibPython.PyType_Type) return TypeObject.new(pyptr) when PyCall::Types.pyisinstance(pyptr, LibPython.PyBool_Type) return Conversions.convert_to_boolean(pyptr) when PyCall::Types.pyisinstance(pyptr, LibPython.PyInt_Type) return Conversions.convert_to_integer(pyptr) when PyCall::Types.pyisinstance(pyptr, LibPython.PyLong_Type) # TODO: should make Bignum when PyCall::Types.pyisinstance(pyptr, LibPython.PyFloat_Type) return Conversions.convert_to_float(pyptr) when PyCall::Types.pyisinstance(pyptr, LibPython.PyComplex_Type) return Conversions.convert_to_complex(pyptr) when PyCall::Types.pyisinstance(pyptr, LibPython.PyString_Type) return Conversions.convert_to_string(pyptr) when PyCall::Types.pyisinstance(pyptr, LibPython.PyUnicode_Type) py_str_ptr = LibPython.PyUnicode_AsUTF8String(pyptr) return Conversions.convert_to_string(py_str_ptr).force_encoding(Encoding::UTF_8) when PyCall::Types.pyisinstance(pyptr, LibPython.PyList_Type) return PyCall::List.new(pyptr) when PyCall::Types.pyisinstance(pyptr, LibPython.PyTuple_Type) return Conversions.convert_to_tuple(pyptr) when PyCall::Types.pyisinstance(pyptr, LibPython.PyDict_Type) return PyCall::Dict.new(pyptr) when PyCall::Types.pyisinstance(pyptr, LibPython.PySet_Type) return PyCall::Set.new(pyptr) end pyobj = PyObject.new(pyptr) each_type_pair do |tp| pytype, rbtype = tp.to_a next unless pyobj.kind_of?(pytype) case when rbtype.kind_of?(Proc) return rbtype.(pyobj) when rbtype.respond_to?(:from_python) return rbtype.from_python(pyobj) else return rbtype.new(pyobj) end end pyobj end def self.from_ruby(obj) case obj when LibPython::PyObjectStruct obj when PyObject, PyObjectWrapper obj.__pyobj__ when TrueClass, FalseClass LibPython.PyBool_FromLong(obj ? 1 : 0) when Integer LibPython.PyInt_FromSsize_t(obj) when Float LibPython.PyFloat_FromDouble(obj) when String if obj.encoding != Encoding::BINARY && (PyCall.unicode_literals? || !obj.ascii_only?) obj = obj.encode(Encoding::UTF_8) if obj.encoding != Encoding::UTF_8 return LibPython.PyUnicode_DecodeUTF8(obj, obj.bytesize, nil) end LibPython.PyString_FromStringAndSize(obj, obj.bytesize) when Symbol from_ruby(obj.to_s) when Array PyCall::List.new(obj).__pyobj__ when Hash PyCall::Dict.new(obj).__pyobj__ when Proc PyCall.wrap_ruby_callable(obj) else PyCall.None end end def self.convert_to_boolean(py_obj) 0 != LibPython.PyInt_AsSsize_t(py_obj) end def self.convert_to_integer(py_obj) LibPython.PyInt_AsSsize_t(py_obj) end def self.convert_to_float(py_obj) LibPython.PyFloat_AsDouble(py_obj) end def self.convert_to_complex(py_obj) real = LibPython.PyComplex_RealAsDouble(py_obj) imag = LibPython.PyComplex_ImagAsDouble(py_obj) Complex(real, imag) end def self.convert_to_string(py_obj) FFI::MemoryPointer.new(:string) do |str_ptr| FFI::MemoryPointer.new(:int) do |len_ptr| res = LibPython.PyString_AsStringAndSize(py_obj, str_ptr, len_ptr) return nil if res == -1 # FIXME: error len = len_ptr.get(:int, 0) return str_ptr.get_pointer(0).read_string(len) end end end def self.convert_to_array(py_obj, force_list: true, array_class: Array) case when force_list || py_obj.kind_of?(LibPython.PyList_Type) len = LibPython.PySequence_Size(py_obj) array_class.new(len) do |i| LibPython.PySequence_GetItem(py_obj, i).to_ruby end end end def self.convert_to_tuple(py_obj) PyCall::Tuple.new(py_obj) end end class LibPython::PyObjectStruct def to_ruby Conversions.to_ruby(self) end end end