From 636cfee28d409a45d705c8702c6f6ec057dfdd2a Mon Sep 17 00:00:00 2001 From: Ewan Higgs Date: Wed, 26 Aug 2015 04:00:01 +0200 Subject: [PATCH] Add tests for sequence. Make list and tuple use private PyObject again. Add from_object that converts from PyObject into PyList and PyTuple. Maybe it should be a macro as part of the newtype thing? --- src/objects/list.rs | 19 ++- src/objects/sequence.rs | 364 ++++++++++++++++++++++++++++++++++++++++ src/objects/tuple.rs | 19 ++- 3 files changed, 398 insertions(+), 4 deletions(-) create mode 100644 src/objects/sequence.rs diff --git a/src/objects/list.rs b/src/objects/list.rs index 14056300..a2313ae9 100644 --- a/src/objects/list.rs +++ b/src/objects/list.rs @@ -17,13 +17,13 @@ // DEALINGS IN THE SOFTWARE. use python::{Python, PythonObject, ToPythonPointer}; -use err::{self, PyResult}; +use err::{self, PyErr, PyResult}; use super::object::PyObject; use ffi::{self, Py_ssize_t}; use conversion::{ToPyObject, ExtractPyObject}; /// Represents a Python `list`. -pub struct PyList<'p>(pub PyObject<'p>); +pub struct PyList<'p>(PyObject<'p>); pyobject_newtype!(PyList, PyList_Check, PyList_Type); @@ -40,6 +40,21 @@ impl <'p> PyList<'p> { } } + /// Construct a list from an existing object. + #[inline] + pub fn from_object(obj: PyObject<'p>) -> PyResult<'p, PyList> { + let py = obj.python(); + let ptr = obj.as_ptr(); + unsafe { + if ffi::PyList_Check(ptr) != 0{ + Ok(PyList(obj)) + } else { + Err(PyErr::fetch(py)) + } + } + } + + /// Gets the length of the list. #[inline] pub fn len(&self) -> usize { diff --git a/src/objects/sequence.rs b/src/objects/sequence.rs new file mode 100644 index 00000000..4cacbc3a --- /dev/null +++ b/src/objects/sequence.rs @@ -0,0 +1,364 @@ +// Copyright (c) 2015 Daniel Grunwald +// +// Permission is hereby granted, free of charge, to any person obtaining a copy of this +// software and associated documentation files (the "Software"), to deal in the Software +// without restriction, including without limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons +// to whom the Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all copies or +// substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use std::mem; +use ffi; +use python::{PythonObject, ToPythonPointer}; +use objects::{PyObject, PyList, PyTuple}; +use ffi::Py_ssize_t; +use err::{PyErr, PyResult, result_from_owned_ptr}; + +pub struct PySequence<'p>(PyObject<'p>); + +pyobject_newtype!(PySequence, PySequence_Check); + +impl <'p> PySequence<'p> { + /// Construct a Sequence from an existing object. + #[inline] + pub fn from_object(obj: PyObject<'p>) -> PyResult<'p, PySequence> { + let py = obj.python(); + let ptr = obj.as_ptr(); + unsafe { + if ffi::PySequence_Check(ptr) != 0{ + Ok(PySequence(obj)) + } else { + Err(PyErr::fetch(py)) + } + } + } + + #[inline] + pub fn size(&self) -> PyResult<'p, usize> { + let v = unsafe { ffi::PySequence_Size(self.as_ptr()) }; + if v == -1 { + Err(PyErr::fetch(self.python())) + } else { + Ok(v as usize) + } + } + + #[inline] + pub fn length(&self) -> usize { + let v = unsafe { ffi::PySequence_Length(self.as_ptr()) }; + if v == -1 { + panic!("Received an unexpected error finding the length of a PySequence") + } + v as usize + } + + #[inline] + pub fn concat(&self, other: &PySequence<'p>) -> PyResult<'p, PySequence> { + let seq = try!(unsafe { + let py = self.python(); + result_from_owned_ptr(py, ffi::PySequence_Concat(self.as_ptr(), other.as_ptr())) + }); + Ok(PySequence(seq)) + } + + #[inline] + pub fn repeat(&self, count: usize) -> PyResult<'p, PySequence> { + let seq = try!(unsafe { + let py = self.python(); + result_from_owned_ptr(py, ffi::PySequence_Repeat(self.as_ptr(), count as Py_ssize_t)) + }); + Ok(PySequence(seq)) + } + + #[inline] + pub fn in_place_concat(&self, other: &PySequence<'p>) -> PyResult<'p, PySequence> { + let seq = try!(unsafe { + let py = self.python(); + result_from_owned_ptr(py, ffi::PySequence_InPlaceConcat(self.as_ptr(), other.as_ptr())) + }); + Ok(PySequence(seq)) + } + + #[inline] + pub fn in_place_repeat(&self, count: usize) -> PyResult<'p, PySequence> { + let seq = try!(unsafe { + let py = self.python(); + result_from_owned_ptr(py, + ffi::PySequence_InPlaceRepeat(self.as_ptr(), count as Py_ssize_t)) + }); + Ok(PySequence(seq)) + } + + #[inline] + pub fn get_item(&self, index: usize) -> PyObject<'p> { + assert!(index < self.length()); + unsafe { + let py = self.python(); + PyObject::from_borrowed_ptr(py, ffi::PySequence_GetItem(self.as_ptr(), index as Py_ssize_t)) + } + } + + #[inline] + pub fn get_slice(&self, index: usize) -> PyResult<'p, PySequence> { + let slice = try!(unsafe { + let py = self.python(); + result_from_owned_ptr(py, + ffi::PySequence_GetItem(self.as_ptr(), index as Py_ssize_t)) + }); + Ok(PySequence(slice)) + } + + #[inline] + pub fn set_item(&self, i: usize, v: &PyObject<'p>) -> PyResult<'p, ()> { + let v = unsafe { + ffi::PySequence_SetItem(self.as_ptr(), i as Py_ssize_t, v.as_ptr()) + }; + if v == -1 { + Err(PyErr::fetch(self.python())) + } else{ + Ok(()) + } + } + + #[inline] + pub fn del_item(&self, i: usize) -> PyResult<'p, ()> { + let v = unsafe { ffi::PySequence_DelItem(self.as_ptr(), i as Py_ssize_t) }; + if v == -1 { + Err(PyErr::fetch(self.python())) + } else { + Ok(()) + } + } + + #[inline] + pub fn set_slice(&self, i1: usize, i2: usize, v: &PyObject<'p>) -> PyResult<'p, ()> { + let v = unsafe { + ffi::PySequence_SetSlice(self.as_ptr(), i1 as Py_ssize_t, i2 as Py_ssize_t, v.as_ptr()) + }; + if v == -1 { + Err(PyErr::fetch(self.python())) + } else{ + Ok(()) + } + } + + #[inline] + pub fn del_slice(&self, i1: i64, i2: i64) -> PyResult<'p, ()> { + let v = unsafe { ffi::PySequence_DelSlice(self.as_ptr(), i1, i2) }; + if v == -1 { + Err(PyErr::fetch(self.python())) + } else { + Ok(()) + } + } + + #[inline] + pub fn count(&self, v: &PyObject<'p>) -> PyResult<'p, usize> { + let v = unsafe { ffi::PySequence_Count(self.as_ptr(), v.as_ptr()) }; + if v == -1 { + Err(PyErr::fetch(self.python())) + } else { + Ok(v as usize) + } + } + + #[inline] + pub fn contains(&self, v: &PyObject<'p>) -> PyResult<'p, bool> { + let v = unsafe { ffi::PySequence_Contains(self.as_ptr(), v.as_ptr()) }; + match v { + 0 => Ok(false), + 1 => Ok(true), + _ => Err(PyErr::fetch(self.python())) + } + } + + #[inline] + pub fn index(&self, v: &PyObject<'p>) -> PyResult<'p, usize> { + let v = unsafe { ffi::PySequence_Index(self.as_ptr(), v.as_ptr()) }; + if v == -1 { + Err(PyErr::fetch(self.python())) + } else { + Ok(v as usize) + } + } + + #[inline] + pub fn list(&self) -> PyResult<'p, PyList> { + let v = try!(unsafe { + let py = self.python(); + result_from_owned_ptr(py, ffi::PySequence_List(self.as_ptr())) + }); + PyList::from_object(v) + } + + #[inline] + pub fn tuple(&self) -> PyResult<'p, PyTuple> { + let v = try!(unsafe { + let py = self.python(); + result_from_owned_ptr(py, ffi::PySequence_Tuple(self.as_ptr())) + }); + PyTuple::from_object(v) + } +} + +pub struct PySequenceIterator<'p> { + sequence : PySequence<'p>, + index : usize +} + +impl <'p> IntoIterator for PySequence<'p> { + type Item = PyObject<'p>; + type IntoIter = PySequenceIterator<'p>; + + fn into_iter(self) -> PySequenceIterator<'p> { + PySequenceIterator{ sequence: self, index: 0 } + } +} + +impl <'a, 'p> IntoIterator for &'a PySequence<'p> { + type Item = PyObject<'p>; + type IntoIter = PySequenceIterator<'p>; + + #[inline] + fn into_iter(self) -> PySequenceIterator<'p> { + PySequenceIterator{ sequence: self.clone(), index: 0 } + } +} + +impl <'p> Iterator for PySequenceIterator<'p> { + type Item = PyObject<'p>; + + #[inline] + fn next(&mut self) -> Option> { + // can't report any errors in underlying size check so we panic. + let len = self.sequence.length(); + if self.index < len { + let item = self.sequence.get_item(self.index) ; + self.index += 1; + Some(item) + } else { + None + } + } +} + +#[cfg(test)] +mod test { + use std; + use python::{Python, PythonObject}; + use conversion::ToPyObject; + use objects::PySequence; + + #[test] + fn test_numbers_are_not_sequences() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = 42i32; + match PySequence::from_object(v.to_py_object(py).into_object()) { + Ok(_) => panic!(), // We shouldn't be able to make a sequence from a number! + Err(_) => assert!(true) + }; + } + + #[test] + fn test_strings_are_sequences() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = "London Calling"; + match PySequence::from_object(v.to_py_object(py).into_object()) { + Ok(_) => assert!(true), + Err(_) => panic!() + }; + } + #[test] + fn test_seq_empty() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v : Vec = vec![]; + let seq = PySequence::from_object(v.to_py_object(py).into_object()).unwrap(); + assert_eq!(0, seq.length()); + assert_eq!(0, seq.size().unwrap()); + + let needle = 7i32.to_py_object(py).into_object(); + assert_eq!(false, seq.contains(&needle).unwrap()); + } + + #[test] + fn test_seq_filled() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v : Vec = vec![1, 1, 2, 3, 5, 8]; + let seq = PySequence::from_object(v.to_py_object(py).into_object()).unwrap(); + assert_eq!(6, seq.length()); + assert_eq!(6, seq.size().unwrap()); + + let bad_needle = 7i32.to_py_object(py).into_object(); + assert_eq!(false, seq.contains(&bad_needle).unwrap()); + + let good_needle = 8i32.to_py_object(py).into_object(); + assert_eq!(true, seq.contains(&good_needle).unwrap()); + + let type_coerced_needle = 8f32.to_py_object(py).into_object(); + assert_eq!(true, seq.contains(&type_coerced_needle).unwrap()); + } + + #[test] + fn test_seq_strings() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = vec!["It", "was", "the", "worst", "of", "times"]; + let seq = PySequence::from_object(v.to_py_object(py).into_object()).unwrap(); + + let bad_needle = "blurst".to_py_object(py).into_object(); + assert_eq!(false, seq.contains(&bad_needle).unwrap()); + + let good_needle = "worst".to_py_object(py).into_object(); + assert_eq!(true, seq.contains(&good_needle).unwrap()); + } + + #[test] + fn test_seq_concat() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v : Vec = vec![1, 2, 3]; + let concat_v : Vec = vec![1, 2, 3, 1, 2, 3]; + let seq = PySequence::from_object(v.to_py_object(py).into_object()).unwrap(); + let concat_seq = seq.concat(&seq).unwrap(); + assert_eq!(6, concat_seq.length()); + assert_eq!(concat_v, concat_seq.into_object().extract::>().unwrap()); + } + + #[test] + fn test_seq_concat_string() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = "string"; + let concat_v = "stringstring"; + let seq = PySequence::from_object(v.to_py_object(py).into_object()).unwrap(); + let concat_seq = seq.concat(&seq).unwrap(); + assert_eq!(12, concat_seq.length()); + //assert_eq!(concat_v, concat_seq.into_object().extract::().unwrap()); + } + + + #[test] + fn test_seq_repeat() { + let gil = Python::acquire_gil(); + let py = gil.python(); + let v = vec!["foo", "bar"]; + let repeated = vec!["foo", "bar", "foo", "bar", "foo", "bar"]; + let seq = PySequence::from_object(v.to_py_object(py).into_object()).unwrap(); + let repeat_seq = seq.repeat(3).unwrap(); + assert_eq!(6, repeat_seq.length()); + //assert_eq!(repeated, repeat_seq.into_object().extract::>().unwrap()); + } +} diff --git a/src/objects/tuple.rs b/src/objects/tuple.rs index 04d8dff3..0fc3f480 100644 --- a/src/objects/tuple.rs +++ b/src/objects/tuple.rs @@ -17,14 +17,14 @@ // DEALINGS IN THE SOFTWARE. use python::{Python, PythonObject, ToPythonPointer}; -use err::{self, PyResult, PyErr}; +use err::{self, PyErr, PyResult}; use super::object::PyObject; use super::exc; use ffi::{self, Py_ssize_t}; use conversion::{ToPyObject, ExtractPyObject}; /// Represents a Python tuple object. -pub struct PyTuple<'p>(pub PyObject<'p>); +pub struct PyTuple<'p>(PyObject<'p>); pyobject_newtype!(PyTuple, PyTuple_Check, PyTuple_Type); @@ -42,6 +42,21 @@ impl <'p> PyTuple<'p> { } } + /// Construct a tuple from an existing object. + #[inline] + pub fn from_object(obj: PyObject<'p>) -> PyResult<'p, PyTuple> { + let py = obj.python(); + let ptr = obj.as_ptr(); + unsafe { + if ffi::PyTuple_Check(ptr) != 0{ + Ok(PyTuple(obj)) + } else { + Err(PyErr::fetch(py)) + } + } + } + + /// Retrieves the empty tuple. pub fn empty(py: Python<'p>) -> PyTuple<'p> { unsafe {