1use crate::parser::raw_match::RawMatchData;
2use pyo3::exceptions::{PyIndexError, PyTypeError};
3use pyo3::prelude::*;
4use pyo3::types::PyList;
5use pyo3::IntoPyObjectExt;
6
7#[pyclass]
11pub struct Results {
12 raw_data: Vec<RawMatchData>,
13 cached_results: Option<PyObject>,
15}
16
17impl Results {
18 pub fn new(raw_data: Vec<RawMatchData>) -> Self {
19 Self {
20 raw_data,
21 cached_results: None,
22 }
23 }
24
25 fn convert_all(&mut self, py: Python) -> PyResult<PyObject> {
27 if let Some(ref cached) = self.cached_results {
28 return Ok(cached.clone_ref(py));
29 }
30
31 let mut py_results: Vec<PyObject> = Vec::with_capacity(self.raw_data.len());
32 for raw_data in &self.raw_data {
33 let parse_result = raw_data.to_parse_result(py)?;
34 py_results.push(parse_result.into_py_any(py)?);
35 }
36
37 let items: Vec<_> = py_results.iter().map(|obj| obj.bind(py)).collect();
38 let results_list = PyList::new(py, items)?;
39 let list_obj = results_list.into_py_any(py)?;
40
41 self.cached_results = Some(list_obj.clone_ref(py));
43 Ok(list_obj)
44 }
45
46 pub fn convert_item(&self, index: usize, py: Python) -> PyResult<PyObject> {
48 if index >= self.raw_data.len() {
49 return Err(PyIndexError::new_err("list index out of range"));
50 }
51
52 let raw_data = &self.raw_data[index];
53 let parse_result = raw_data.to_parse_result(py)?;
54 parse_result.into_py_any(py)
55 }
56}
57
58#[pymethods]
59impl Results {
60 fn __len__(&self) -> usize {
62 self.raw_data.len()
63 }
64
65 fn __getitem__(&self, key: &Bound<'_, PyAny>, py: Python) -> PyResult<PyObject> {
67 if let Ok(index) = key.extract::<usize>() {
69 self.convert_item(index, py)
71 } else if let Ok(index) = key.extract::<isize>() {
72 let len = self.raw_data.len();
73 let actual_index = if index < 0 {
74 match (len as i128).checked_add(index as i128) {
75 Some(sum) if sum >= 0 && (sum as usize) < len => sum as usize,
76 _ => {
77 return Err(PyIndexError::new_err("list index out of range"));
78 }
79 }
80 } else {
81 let u = index as usize;
82 if u >= len {
83 return Err(PyIndexError::new_err("list index out of range"));
84 }
85 u
86 };
87 self.convert_item(actual_index, py)
88 } else if key.is_instance_of::<pyo3::types::PySlice>() {
89 let mut results = Results::new(self.raw_data.clone());
92 let list = results.convert_all(py)?;
93 let list_bound = list.bind(py);
95 let slice_result = list_bound.get_item(key)?;
96 Ok(slice_result.into_py_any(py)?)
97 } else {
98 Err(PyTypeError::new_err(
99 "list indices must be integers or slices",
100 ))
101 }
102 }
103
104 fn __iter__(slf: PyRef<'_, Self>) -> PyResult<ResultsIterator> {
106 Ok(ResultsIterator {
107 results: slf.into(),
108 cached_list: None,
109 index: 0,
110 })
111 }
112
113 #[allow(clippy::wrong_self_convention)] fn to_list(&mut self, py: Python) -> PyResult<PyObject> {
117 self.convert_all(py)
118 }
119
120 fn __repr__(&self) -> String {
122 format!("<Results {} matches>", self.raw_data.len())
123 }
124
125 fn __str__(&self) -> String {
126 self.__repr__()
127 }
128}
129
130#[pyclass]
133pub struct ResultsIterator {
134 results: Py<Results>,
135 cached_list: Option<PyObject>, index: usize,
137}
138
139#[pymethods]
140impl ResultsIterator {
141 fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
142 slf
143 }
144
145 fn __next__(&mut self, py: Python) -> PyResult<Option<PyObject>> {
146 if self.cached_list.is_none() {
148 let results = self.results.bind(py);
149 let list = results.call_method0("to_list")?;
151 self.cached_list = Some(list.into_py_any(py)?);
152 }
153
154 let list_bound = self
156 .cached_list
157 .as_ref()
158 .expect("cached_list set immediately above when None")
159 .bind(py)
160 .downcast::<pyo3::types::PyList>()?;
161 let len = list_bound.len();
162
163 if self.index >= len {
164 return Ok(None);
165 }
166
167 let item = list_bound.get_item(self.index)?;
168 self.index += 1;
169 Ok(Some(item.into_py_any(py)?))
170 }
171}