uv_migrator/utils/
author.rs1use crate::error::{Error, Result};
2use crate::migrators::setup_py::SetupPyMigrationSource;
3use std::path::Path;
4use toml_edit::DocumentMut;
5
6#[derive(Debug)]
7pub struct Author {
8 pub name: String,
9 pub email: Option<String>,
10}
11
12pub fn extract_authors_from_setup_py(project_dir: &Path) -> Result<Vec<Author>> {
13 let setup_py_path = project_dir.join("setup.py");
14 if !setup_py_path.exists() {
15 return Ok(vec![]);
16 }
17
18 let content = std::fs::read_to_string(&setup_py_path).map_err(|e| Error::FileOperation {
19 path: setup_py_path.clone(),
20 message: format!("Failed to read setup.py: {}", e),
21 })?;
22
23 let mut authors = Vec::new();
24
25 if let Some(start_idx) = content.find("setup(") {
27 let bracket_content = SetupPyMigrationSource::extract_setup_content(&content[start_idx..])?;
28
29 if let Some(name) = SetupPyMigrationSource::extract_parameter(&bracket_content, "author") {
30 let email = SetupPyMigrationSource::extract_parameter(&bracket_content, "author_email");
31 authors.push(Author { name, email });
32 }
33 }
34
35 Ok(authors)
36}
37
38pub fn extract_authors_from_poetry(project_dir: &Path) -> Result<Vec<Author>> {
39 let old_pyproject_path = project_dir.join("old.pyproject.toml");
40 if !old_pyproject_path.exists() {
41 return Ok(vec![]);
42 }
43
44 let content =
45 std::fs::read_to_string(&old_pyproject_path).map_err(|e| Error::FileOperation {
46 path: old_pyproject_path.clone(),
47 message: format!("Failed to read old.pyproject.toml: {}", e),
48 })?;
49
50 let doc = content.parse::<DocumentMut>().map_err(Error::Toml)?;
51
52 if let Some(project) = doc.get("project") {
54 if let Some(authors_array) = project.get("authors").and_then(|a| a.as_array()) {
55 let mut results = Vec::new();
56 for author_value in authors_array.iter() {
57 if let Some(author_str) = author_value.as_str() {
58 results.push(parse_author_string(author_str));
59 } else if let Some(author_table) = author_value.as_inline_table() {
60 let name = author_table
62 .get("name")
63 .and_then(|n| n.as_str())
64 .unwrap_or("Unknown")
65 .to_string();
66
67 let email = author_table
68 .get("email")
69 .and_then(|e| e.as_str())
70 .map(|s| s.to_string());
71
72 results.push(Author { name, email });
73 }
74 }
75 return Ok(results);
76 }
77 }
78
79 let authors = match doc
81 .get("tool")
82 .and_then(|t| t.get("poetry"))
83 .and_then(|poetry| poetry.get("authors"))
84 {
85 Some(array) => {
86 let mut result = Vec::new();
87 if let Some(arr) = array.as_array() {
88 for value in arr.iter() {
89 if let Some(author_str) = value.as_str() {
90 result.push(parse_author_string(author_str));
91 }
92 }
93 }
94 result
95 }
96 None => vec![],
97 };
98
99 Ok(authors)
100}
101
102fn parse_author_string(author_str: &str) -> Author {
103 let author_str = author_str.trim();
104
105 if author_str.starts_with('{') && author_str.ends_with('}') {
107 let content = &author_str[1..author_str.len() - 1];
109 let mut name = String::new();
110 let mut email = None;
111
112 for part in content.split(',') {
113 let part = part.trim();
114 if let Some(name_part) = part
115 .strip_prefix("name = ")
116 .or_else(|| part.strip_prefix("name="))
117 {
118 name = name_part.trim_matches(&['"', '\''][..]).to_string();
119 }
120 if let Some(email_part) = part
121 .strip_prefix("email = ")
122 .or_else(|| part.strip_prefix("email="))
123 {
124 email = Some(email_part.trim_matches(&['"', '\''][..]).to_string());
125 }
126 }
127
128 return Author { name, email };
129 }
130
131 let (name, email) = match (author_str.rfind('<'), author_str.rfind('>')) {
133 (Some(start), Some(end)) if start < end => {
134 let name = author_str[..start].trim().to_string();
135 let email = author_str[start + 1..end].trim().to_string();
136 (name, Some(email))
137 }
138 _ => (author_str.to_string(), None),
139 };
140
141 Author { name, email }
142}