1use crate::error;
2use crate::parser::findall_iter::FindallIter;
3use crate::parser::format_parser::{CompiledFields, FormatParser};
4use crate::pattern_cache::get_or_create_parser;
5use fancy_regex::Regex;
6use formatparse_core::count_capturing_groups;
7use formatparse_core::parser::validate_input_length;
8use formatparse_core::{FieldSpec, FieldType};
9use pyo3::exceptions::PyValueError;
10use pyo3::prelude::*;
11use pyo3::types::{PyAnyMethods, PyDict, PyList, PyString, PyTuple};
12use pyo3::IntoPyObjectExt;
13use std::collections::HashMap;
14
15fn merge_call_extra_types(
16 py: Python<'_>,
17 stored: &Option<HashMap<String, Py<PyAny>>>,
18 provided: Option<HashMap<String, Py<PyAny>>>,
19) -> PyResult<Option<HashMap<String, Py<PyAny>>>> {
20 let mut merged = if let Some(ref stored) = stored {
21 stored
22 .iter()
23 .map(|(k, v)| (k.clone(), v.clone_ref(py)))
24 .collect()
25 } else {
26 HashMap::new()
27 };
28 if let Some(provided) = provided {
29 for (k, v) in provided {
30 merged.insert(k.clone(), v.clone_ref(py));
31 }
32 }
33 Ok(Some(merged))
34}
35
36fn field_type_validation_tag(spec: &FieldSpec) -> String {
37 match &spec.field_type {
38 FieldType::String => "s".to_string(),
39 FieldType::Integer => "d".to_string(),
40 FieldType::Float => "f".to_string(),
41 FieldType::Boolean => "b".to_string(),
42 FieldType::Letters => "l".to_string(),
43 FieldType::Word => "w".to_string(),
44 FieldType::NonLetters => "W".to_string(),
45 FieldType::NonWhitespace => "S".to_string(),
46 FieldType::NonDigits => "D".to_string(),
47 FieldType::Custom(name) => name.clone(),
48 FieldType::Nested => "s".to_string(),
49 FieldType::Multiline | FieldType::IndentBlock => "s".to_string(),
50 FieldType::BracedContent => "s".to_string(),
51 FieldType::NumberWithThousands => "n".to_string(),
52 FieldType::Scientific => "e".to_string(),
53 FieldType::GeneralNumber => "g".to_string(),
54 FieldType::Percentage => "%".to_string(),
55 FieldType::DateTimeISO => "ti".to_string(),
56 FieldType::DateTimeRFC2822 => "te".to_string(),
57 FieldType::DateTimeGlobal => "tg".to_string(),
58 FieldType::DateTimeUS => "ta".to_string(),
59 FieldType::DateTimeCtime => "tc".to_string(),
60 FieldType::DateTimeHTTP => "th".to_string(),
61 FieldType::DateTimeTime => "tt".to_string(),
62 FieldType::DateTimeSystem => "ts".to_string(),
63 FieldType::DateTimeStrftime => "%".to_string(),
64 }
65}
66
67#[pymethods]
68impl FormatParser {
69 #[new]
70 #[pyo3(signature = (pattern=None, extra_types=None))]
71 fn new_py(
72 pattern: Option<&str>,
73 extra_types: Option<HashMap<String, Py<PyAny>>>,
74 ) -> PyResult<Self> {
75 match pattern {
76 Some(p) => Self::new_with_extra_types(p, extra_types),
77 None => {
78 let empty_regex =
81 Regex::new("^$").map_err(|e| crate::error::regex_error(&e.to_string()))?;
82 Ok(Self {
83 pattern: String::new(),
84 regex: empty_regex.clone(),
85 regex_str: String::new(),
86 regex_case_insensitive: None,
87 search_regex: empty_regex.clone(),
88 search_regex_case_insensitive: None,
89 fields: CompiledFields {
90 field_specs: Vec::new(),
91 field_names: Vec::new(),
92 normalized_names: Vec::new(),
93 custom_type_groups: Vec::new(),
94 has_nested_dict_fields: Vec::new(),
95 nested_parsers: Vec::new(),
96 field_count: 0,
97 },
98 name_mapping: HashMap::new(),
99 stored_extra_types: None,
100 allows_empty_default_string_match: false,
101 })
102 }
103 }
104 }
105
106 #[pyo3(signature = (string, case_sensitive=false, extra_types=None, evaluate_result=true))]
111 fn parse(
112 &self,
113 string: &str,
114 case_sensitive: bool,
115 extra_types: Option<HashMap<String, Py<PyAny>>>,
116 evaluate_result: bool,
117 ) -> PyResult<Option<Py<PyAny>>> {
118 validate_input_length(string).map_err(PyValueError::new_err)?;
120
121 if string.contains('\0') {
123 return Err(PyValueError::new_err("Input string contains null byte"));
124 }
125 let merged_extra_types = Python::attach(|py| {
126 merge_call_extra_types(py, &self.stored_extra_types, extra_types)
127 })?;
128 let parser = Python::attach(|py| -> PyResult<std::sync::Arc<FormatParser>> {
129 let cache_et = merged_extra_types.as_ref().map(|m| {
130 m.iter()
131 .map(|(k, v)| (k.clone(), v.clone_ref(py)))
132 .collect()
133 });
134 get_or_create_parser(&self.pattern, cache_et)
135 })?;
136 parser.parse_internal(
137 string,
138 case_sensitive,
139 merged_extra_types.as_ref(),
140 evaluate_result,
141 )
142 }
143
144 #[getter]
146 fn named_fields(&self) -> Vec<String> {
147 self.fields
149 .normalized_names
150 .iter()
151 .filter_map(|n| n.clone())
152 .collect()
153 }
154
155 #[getter]
157 fn field_constraints(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
158 let list = PyList::empty(py);
159 for (i, spec) in self.fields.field_specs.iter().enumerate() {
160 let dict = PyDict::new(py);
161 match self.fields.field_names.get(i).and_then(|n| n.as_deref()) {
162 Some(name) => dict.set_item("name", name)?,
163 None => dict.set_item("name", py.None())?,
164 }
165 dict.set_item("type", field_type_validation_tag(spec))?;
166 match spec.width {
167 Some(w) => dict.set_item("width", w as i64)?,
168 None => dict.set_item("width", py.None())?,
169 }
170 match spec.precision {
171 Some(p) => dict.set_item("precision", p as i64)?,
172 None => dict.set_item("precision", py.None())?,
173 }
174 list.append(dict)?;
175 }
176 list.into_py_any(py)
177 }
178
179 #[getter]
186 fn regex_subpattern(&self) -> String {
187 self.regex_str.clone()
188 }
189
190 #[getter]
195 fn regex_capturing_group_count(&self) -> usize {
196 count_capturing_groups(&self.regex_str)
197 }
198
199 #[getter]
202 fn _expression(&self) -> String {
203 let mut result = self.regex_str.clone();
204
205 result = result.replace(r")\s+(", ") (");
208 result = result.replace(r")\s*(", ") (");
210
211 result = result.replace(
216 r"([+-]?(?:\d+\.\d+|\.\d+|\d+\.)(?:[eE][+-]?\d+)?)",
217 r"([-+ ]?\d*\.\d+)",
218 );
219
220 if result.starts_with("(") && result.ends_with(")") {
224 let inner = &result[1..result.len() - 1];
225 if inner.starts_with(" *(") && inner.ends_with(")") {
227 result = inner.to_string();
229 }
230 }
231
232 result
233 }
234
235 #[getter]
237 fn format(&self) -> Format {
238 Format {
239 pattern: self.pattern.clone(),
240 }
241 }
242
243 #[pyo3(signature = (string, case_sensitive=true, extra_types=None, evaluate_result=true))]
248 fn search(
249 &self,
250 string: &str,
251 case_sensitive: bool,
252 extra_types: Option<HashMap<String, Py<PyAny>>>,
253 evaluate_result: bool,
254 ) -> PyResult<Option<Py<PyAny>>> {
255 validate_input_length(string).map_err(PyValueError::new_err)?;
257
258 if string.contains('\0') {
260 return Err(PyValueError::new_err("Input string contains null byte"));
261 }
262
263 let merged_extra_types = Python::attach(|py| {
264 merge_call_extra_types(py, &self.stored_extra_types, extra_types)
265 })?;
266 let parser = Python::attach(|py| -> PyResult<std::sync::Arc<FormatParser>> {
267 let cache_et = merged_extra_types.as_ref().map(|m| {
268 m.iter()
269 .map(|(k, v)| (k.clone(), v.clone_ref(py)))
270 .collect()
271 });
272 get_or_create_parser(&self.pattern, cache_et)
273 })?;
274 parser.search_pattern(string, case_sensitive, merged_extra_types, evaluate_result)
275 }
276
277 #[pyo3(signature = (string, case_sensitive=false, extra_types=None, evaluate_result=true, max_matches=None))]
284 fn findall_iter(
285 &self,
286 py: Python<'_>,
287 string: &str,
288 case_sensitive: bool,
289 extra_types: Option<HashMap<String, Py<PyAny>>>,
290 evaluate_result: bool,
291 max_matches: Option<usize>,
292 ) -> PyResult<Py<FindallIter>> {
293 validate_input_length(string).map_err(PyValueError::new_err)?;
294
295 if string.contains('\0') {
296 return Err(PyValueError::new_err("Input string contains null byte"));
297 }
298
299 let merged_extra_types = Python::attach(|py| {
300 merge_call_extra_types(py, &self.stored_extra_types, extra_types)
301 })?;
302 let merged_map = merged_extra_types.unwrap_or_default();
303 let parser = Python::attach(|py| -> PyResult<std::sync::Arc<FormatParser>> {
304 let cache_et = if merged_map.is_empty() {
305 None
306 } else {
307 Some(
308 merged_map
309 .iter()
310 .map(|(k, v)| (k.clone(), v.clone_ref(py)))
311 .collect(),
312 )
313 };
314 get_or_create_parser(&self.pattern, cache_et)
315 })?;
316 Py::new(
317 py,
318 FindallIter::new(
319 parser,
320 string.to_string(),
321 case_sensitive,
322 evaluate_result,
323 merged_map,
324 max_matches,
325 ),
326 )
327 }
328
329 fn __reduce__(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
334 let m = py.import("_formatparse")?;
335 let compile_fn = m.getattr("compile")?;
336 let args = PyTuple::new(py, [&self.pattern])?;
337 PyTuple::new(py, [compile_fn.as_any(), args.as_any()])?.into_py_any(py)
338 }
339
340 fn __getstate__(&self, py: Python) -> PyResult<Py<PyAny>> {
342 use pyo3::types::PyDict;
343 let state = PyDict::new(py);
344 state.set_item("pattern", &self.pattern)?;
345 state.into_py_any(py)
346 }
347
348 fn __setstate__(&mut self, _py: Python, state: &Bound<'_, PyAny>) -> PyResult<()> {
350 use pyo3::types::PyDict;
351 let dict = state.cast::<PyDict>()?;
352 let pattern: String = dict
353 .get_item("pattern")?
354 .ok_or_else(|| error::missing_field_error("pattern"))?
355 .extract()?;
356
357 let reconstructed = Self::new_with_extra_types(&pattern, None)?;
359
360 self.pattern = reconstructed.pattern;
362 self.regex_str = reconstructed.regex_str;
363 self.regex = reconstructed.regex;
364 self.regex_case_insensitive = reconstructed.regex_case_insensitive;
365 self.search_regex = reconstructed.search_regex;
366 self.search_regex_case_insensitive = reconstructed.search_regex_case_insensitive;
367 self.fields = reconstructed.fields;
368 self.name_mapping = reconstructed.name_mapping;
369 self.stored_extra_types = reconstructed.stored_extra_types;
370 self.allows_empty_default_string_match = reconstructed.allows_empty_default_string_match;
371 Ok(())
372 }
373}
374
375#[pyclass]
377pub struct Format {
378 pattern: String,
379}
380
381#[pymethods]
382impl Format {
383 fn format(&self, py: Python, args: &Bound<'_, PyAny>) -> PyResult<String> {
385 let pattern_obj = PyString::new(py, &self.pattern);
387 let format_method = pattern_obj.getattr("format")?;
388
389 let result = if let Ok(tuple) = args.cast::<PyTuple>() {
391 format_method.call1(tuple)?
392 } else {
393 format_method.call1((args,))?
395 };
396 result.extract()
397 }
398}