1use std::collections::HashMap;
12
13#[derive(Debug, Clone, PartialEq, Eq, Hash)]
15pub enum PythonLibrary {
16 Requests,
17 Pytest,
18 Pydantic,
19 Logging,
20 Argparse,
21 Json,
22 Datetime,
23 Pathlib,
24 Regex,
25 Os,
26 Sys,
27 Collections,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum RequestsOp {
33 Get,
34 Post,
35 Put,
36 Delete,
37 Patch,
38 Head,
39 Options,
40 Session,
41}
42
43#[derive(Debug, Clone, PartialEq, Eq)]
45pub enum PytestOp {
46 Test,
47 Fixture,
48 Parametrize,
49 Assert,
50 Raises,
51 Skip,
52 Mark,
53}
54
55#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum PydanticOp {
58 BaseModel,
59 Field,
60 Validator,
61 RootValidator,
62 Config,
63}
64
65#[derive(Debug, Clone, PartialEq, Eq)]
67pub enum LoggingOp {
68 Debug,
69 Info,
70 Warning,
71 Error,
72 Critical,
73 Logger,
74}
75
76#[derive(Debug, Clone, PartialEq, Eq)]
78pub enum ArgparseOp {
79 ArgumentParser,
80 AddArgument,
81 ParseArgs,
82}
83
84pub struct CommonLibrariesTranslator {
86 imports: HashMap<PythonLibrary, Vec<String>>,
88 dependencies: HashMap<PythonLibrary, Vec<(&'static str, &'static str)>>,
90}
91
92impl CommonLibrariesTranslator {
93 pub fn new() -> Self {
94 let mut translator = Self {
95 imports: HashMap::new(),
96 dependencies: HashMap::new(),
97 };
98
99 translator.initialize_mappings();
100 translator
101 }
102
103 fn initialize_mappings(&mut self) {
104 self.dependencies.insert(PythonLibrary::Requests, vec![
106 ("reqwest", "0.11"),
107 ("tokio", "1.0"),
108 ]);
109
110 self.dependencies.insert(PythonLibrary::Pytest, vec![]);
112
113 self.dependencies.insert(PythonLibrary::Pydantic, vec![
115 ("serde", "1.0"),
116 ("serde_json", "1.0"),
117 ("validator", "0.16"),
118 ]);
119
120 self.dependencies.insert(PythonLibrary::Logging, vec![
122 ("tracing", "0.1"),
123 ("tracing-subscriber", "0.3"),
124 ]);
125
126 self.dependencies.insert(PythonLibrary::Argparse, vec![
128 ("clap", "4.0"),
129 ]);
130
131 self.dependencies.insert(PythonLibrary::Json, vec![
133 ("serde_json", "1.0"),
134 ]);
135
136 self.dependencies.insert(PythonLibrary::Datetime, vec![
138 ("chrono", "0.4"),
139 ]);
140
141 self.dependencies.insert(PythonLibrary::Regex, vec![
143 ("regex", "1.0"),
144 ]);
145 }
146
147 pub fn translate_requests(&mut self, op: &RequestsOp, args: &[String]) -> String {
149 self.add_import(PythonLibrary::Requests, "use reqwest");
150
151 match op {
152 RequestsOp::Get => {
153 if args.is_empty() {
155 return "reqwest::get(\"url\").await?.text().await?".to_string();
156 }
157 format!("reqwest::get({}).await?.text().await?", args[0])
158 }
159 RequestsOp::Post => {
160 if args.len() >= 2 {
162 format!("reqwest::Client::new().post({}).json(&{}).send().await?", args[0], args[1])
163 } else {
164 "reqwest::Client::new().post(url).send().await?".to_string()
165 }
166 }
167 RequestsOp::Put => {
168 if args.is_empty() {
169 return "reqwest::Client::new().put(url).send().await?".to_string();
170 }
171 format!("reqwest::Client::new().put({}).send().await?", args[0])
172 }
173 RequestsOp::Delete => {
174 if args.is_empty() {
175 return "reqwest::Client::new().delete(url).send().await?".to_string();
176 }
177 format!("reqwest::Client::new().delete({}).send().await?", args[0])
178 }
179 RequestsOp::Patch => {
180 if args.is_empty() {
181 return "reqwest::Client::new().patch(url).send().await?".to_string();
182 }
183 format!("reqwest::Client::new().patch({}).send().await?", args[0])
184 }
185 RequestsOp::Head => {
186 if args.is_empty() {
187 return "reqwest::Client::new().head(url).send().await?".to_string();
188 }
189 format!("reqwest::Client::new().head({}).send().await?", args[0])
190 }
191 RequestsOp::Options => {
192 "reqwest::Client::new().request(Method::OPTIONS, url).send().await?".to_string()
193 }
194 RequestsOp::Session => {
195 "reqwest::Client::new()".to_string()
197 }
198 }
199 }
200
201 pub fn translate_pytest(&mut self, op: &PytestOp, args: &[String]) -> String {
203 match op {
204 PytestOp::Test => {
205 if args.is_empty() {
207 return "#[test]\nfn test_foo() {\n // test body\n}".to_string();
208 }
209 format!("#[test]\nfn {}() {{\n // test body\n}}", args[0])
210 }
211 PytestOp::Fixture => {
212 "// Use setup/teardown or lazy_static for fixtures".to_string()
214 }
215 PytestOp::Parametrize => {
216 "#[test_case::test_case(/* cases */)]".to_string()
218 }
219 PytestOp::Assert => {
220 if args.len() >= 2 {
222 format!("assert_eq!({}, {});", args[0], args[1])
223 } else {
224 "assert!(condition);".to_string()
225 }
226 }
227 PytestOp::Raises => {
228 "#[should_panic(expected = \"...\")]".to_string()
230 }
231 PytestOp::Skip => {
232 "#[ignore]".to_string()
234 }
235 PytestOp::Mark => {
236 "#[cfg_attr(not(feature = \"slow_tests\"), ignore)]".to_string()
238 }
239 }
240 }
241
242 pub fn translate_pydantic(&mut self, op: &PydanticOp, args: &[String]) -> String {
244 self.add_import(PythonLibrary::Pydantic, "use serde::{Serialize, Deserialize}");
245 self.add_import(PythonLibrary::Pydantic, "use validator::Validate");
246
247 match op {
248 PydanticOp::BaseModel => {
249 if args.is_empty() {
251 return "#[derive(Debug, Serialize, Deserialize, Validate)]\nstruct Model {\n // fields\n}".to_string();
252 }
253 format!("#[derive(Debug, Serialize, Deserialize, Validate)]\nstruct {} {{\n // fields\n}}", args[0])
254 }
255 PydanticOp::Field => {
256 if args.len() >= 2 {
258 format!("#[validate({})]\npub {}: {},", args[1], args[0], "T")
259 } else {
260 "pub field: T,".to_string()
261 }
262 }
263 PydanticOp::Validator => {
264 "// Implement custom validation in separate function".to_string()
266 }
267 PydanticOp::RootValidator => {
268 "// Implement validation in struct impl".to_string()
270 }
271 PydanticOp::Config => {
272 "#[serde(rename_all = \"camelCase\")]".to_string()
274 }
275 }
276 }
277
278 pub fn translate_logging(&mut self, op: &LoggingOp, args: &[String]) -> String {
280 self.add_import(PythonLibrary::Logging, "use tracing::{debug, info, warn, error}");
281
282 match op {
283 LoggingOp::Debug => {
284 if args.is_empty() {
285 return "debug!(\"message\");".to_string();
286 }
287 format!("debug!({});", args.join(", "))
288 }
289 LoggingOp::Info => {
290 if args.is_empty() {
291 return "info!(\"message\");".to_string();
292 }
293 format!("info!({});", args.join(", "))
294 }
295 LoggingOp::Warning => {
296 if args.is_empty() {
297 return "warn!(\"message\");".to_string();
298 }
299 format!("warn!({});", args.join(", "))
300 }
301 LoggingOp::Error => {
302 if args.is_empty() {
303 return "error!(\"message\");".to_string();
304 }
305 format!("error!({});", args.join(", "))
306 }
307 LoggingOp::Critical => {
308 if args.is_empty() {
309 return "error!(\"CRITICAL: message\");".to_string();
310 }
311 format!("error!(\"CRITICAL: {{}}\", {});", args.join(", "))
312 }
313 LoggingOp::Logger => {
314 "// Use tracing_subscriber::fmt::init()".to_string()
316 }
317 }
318 }
319
320 pub fn translate_argparse(&mut self, op: &ArgparseOp, args: &[String]) -> String {
322 self.add_import(PythonLibrary::Argparse, "use clap::Parser");
323
324 match op {
325 ArgparseOp::ArgumentParser => {
326 "#[derive(Parser, Debug)]\n#[command(author, version, about)]\nstruct Args {\n // fields\n}".to_string()
328 }
329 ArgparseOp::AddArgument => {
330 if args.is_empty() {
332 return "#[arg(short, long)]\nfield: String,".to_string();
333 }
334 format!("#[arg({})]\n{}: String,", args[0], args.get(1).unwrap_or(&"field".to_string()))
335 }
336 ArgparseOp::ParseArgs => {
337 "Args::parse()".to_string()
339 }
340 }
341 }
342
343 pub fn translate_datetime(&self, operation: &str, args: &[String]) -> String {
345 match operation {
346 "now" => {
347 "chrono::Utc::now()".to_string()
349 }
350 "date" => {
351 if args.len() >= 3 {
353 format!("chrono::NaiveDate::from_ymd_opt({}, {}, {}).unwrap()", args[0], args[1], args[2])
354 } else {
355 "chrono::NaiveDate::from_ymd_opt(2024, 1, 1).unwrap()".to_string()
356 }
357 }
358 "strftime" => {
359 if args.is_empty() {
361 return "dt.format(\"%Y-%m-%d\").to_string()".to_string();
362 }
363 format!("dt.format({}).to_string()", args[0])
364 }
365 "strptime" => {
366 if args.len() >= 2 {
368 format!("chrono::NaiveDateTime::parse_from_str({}, {}).unwrap()", args[0], args[1])
369 } else {
370 "chrono::NaiveDateTime::parse_from_str(s, \"%Y-%m-%d\").unwrap()".to_string()
371 }
372 }
373 "timedelta" => {
374 "chrono::Duration::days(1)".to_string()
376 }
377 _ => format!("/* datetime.{} */", operation),
378 }
379 }
380
381 pub fn translate_pathlib(&self, operation: &str, args: &[String]) -> String {
383 match operation {
384 "Path" => {
385 if args.is_empty() {
387 return "std::path::Path::new(\"file.txt\")".to_string();
388 }
389 format!("std::path::Path::new({})", args[0])
390 }
391 "exists" => {
392 "path.exists()".to_string()
394 }
395 "is_file" => {
396 "path.is_file()".to_string()
397 }
398 "is_dir" => {
399 "path.is_dir()".to_string()
400 }
401 "read_text" => {
402 "std::fs::read_to_string(path)?".to_string()
404 }
405 "write_text" => {
406 if args.is_empty() {
408 return "std::fs::write(path, content)?".to_string();
409 }
410 format!("std::fs::write(path, {})?", args[0])
411 }
412 "mkdir" => {
413 "std::fs::create_dir(path)?".to_string()
415 }
416 "rmdir" => {
417 "std::fs::remove_dir(path)?".to_string()
418 }
419 "glob" => {
420 if args.is_empty() {
422 return "glob::glob(\"*.txt\")?".to_string();
423 }
424 format!("glob::glob({})?", args[0])
425 }
426 _ => format!("/* pathlib.{} */", operation),
427 }
428 }
429
430 pub fn translate_regex(&self, operation: &str, args: &[String]) -> String {
432 match operation {
433 "compile" => {
434 if args.is_empty() {
436 return "regex::Regex::new(r\"pattern\")?".to_string();
437 }
438 format!("regex::Regex::new({})?", args[0])
439 }
440 "match" | "search" => {
441 if args.len() >= 2 {
443 format!("regex::Regex::new({})?.is_match({})", args[0], args[1])
444 } else {
445 "regex.is_match(text)".to_string()
446 }
447 }
448 "findall" => {
449 "regex.find_iter(text).map(|m| m.as_str()).collect::<Vec<_>>()".to_string()
451 }
452 "sub" => {
453 if args.len() >= 2 {
455 format!("regex.replace_all({}, {})", args[1], args[0])
456 } else {
457 "regex.replace_all(text, replacement)".to_string()
458 }
459 }
460 _ => format!("/* re.{} */", operation),
461 }
462 }
463
464 fn add_import(&mut self, lib: PythonLibrary, import: &str) {
466 self.imports.entry(lib)
467 .or_default()
468 .push(import.to_string());
469 }
470
471 pub fn get_imports(&self, lib: &PythonLibrary) -> Vec<String> {
473 self.imports.get(lib).cloned().unwrap_or_default()
474 }
475
476 pub fn get_cargo_dependencies(&self) -> Vec<(&'static str, &'static str)> {
478 let mut deps = Vec::new();
479 for lib_deps in self.dependencies.values() {
480 deps.extend(lib_deps.iter().copied());
481 }
482 deps.sort();
483 deps.dedup();
484 deps
485 }
486
487 pub fn get_library_dependencies(&self, lib: &PythonLibrary) -> Vec<(&'static str, &'static str)> {
489 self.dependencies.get(lib).cloned().unwrap_or_default()
490 }
491}
492
493impl Default for CommonLibrariesTranslator {
494 fn default() -> Self {
495 Self::new()
496 }
497}
498
499#[cfg(test)]
500mod tests {
501 use super::*;
502
503 #[test]
504 fn test_requests_get() {
505 let mut translator = CommonLibrariesTranslator::new();
506 let result = translator.translate_requests(&RequestsOp::Get, &["\"https://api.example.com\"".to_string()]);
507 assert!(result.contains("reqwest::get"));
508 assert!(result.contains("await"));
509 }
510
511 #[test]
512 fn test_pytest_test() {
513 let mut translator = CommonLibrariesTranslator::new();
514 let result = translator.translate_pytest(&PytestOp::Test, &["test_example".to_string()]);
515 assert!(result.contains("#[test]"));
516 assert!(result.contains("test_example"));
517 }
518
519 #[test]
520 fn test_pydantic_model() {
521 let mut translator = CommonLibrariesTranslator::new();
522 let result = translator.translate_pydantic(&PydanticOp::BaseModel, &["User".to_string()]);
523 assert!(result.contains("Serialize"));
524 assert!(result.contains("Deserialize"));
525 assert!(result.contains("User"));
526 }
527
528 #[test]
529 fn test_logging() {
530 let mut translator = CommonLibrariesTranslator::new();
531 let result = translator.translate_logging(&LoggingOp::Info, &["\"message\"".to_string()]);
532 assert!(result.contains("info!"));
533 }
534
535 #[test]
536 fn test_datetime() {
537 let translator = CommonLibrariesTranslator::new();
538 let result = translator.translate_datetime("now", &[]);
539 assert!(result.contains("chrono"));
540 }
541}