use std::env;
use std::error::Error;
use std::path::PathBuf;
use toml_edit::DocumentMut;
#[derive(Debug)]
pub struct ProjectInfo{
pub project_name:String,
pub module_name:String,
pub output_dir:PathBuf,
pub rust_src_file:Vec<PathBuf>,
}
pub fn locate_python_project()->Result<Vec<ProjectInfo>, Box<dyn Error>>{
let current_dir = env::current_dir().expect("現在のディレクトリを取得できませんでした");
let mut project_infos = vec![];
get_maturin_project_info(current_dir.to_path_buf(),&mut project_infos)?;
Ok(project_infos)
}
fn get_maturin_project_info(project_root:PathBuf,project_infos:&mut Vec<ProjectInfo>)->Result<(), Box<dyn Error>>{
if project_root.join("pyproject.toml").exists(){
let pyproject_toml = std::fs::read_to_string(project_root.join("pyproject.toml")).unwrap();
let doc = pyproject_toml.parse::<DocumentMut>().expect("TOMLパース失敗");
if let Some(uv_workspace) = doc.get("tool")
.and_then(|tool| tool.get("uv"))
.and_then(|uv| uv.get("workspace")){
let members = get_workspace_members_path(uv_workspace,project_root);
for member in members {
get_maturin_project_info(member, project_infos)?;
}
}
else{
if is_maturin_project(&doc){
let cargo_toml = std::fs::read_to_string(project_root.join("Cargo.toml")).unwrap();
let cargo_doc = cargo_toml.parse::<DocumentMut>().unwrap();
let mut rust_src_files = vec![];
get_rust_src_files(project_root.join("src"),&mut rust_src_files)?;
let output_dir = project_root.join("src").join(get_project_name(&cargo_doc));
project_infos.push(ProjectInfo {
project_name: get_project_name(&cargo_doc),
module_name: get_module_name(&cargo_doc),
rust_src_file: rust_src_files,
output_dir: output_dir
});
}
}
}
else{
return Err(format!("{}はpyproject.tomlを発見できなかったため解析できませんでした", project_root.display()).into());
}
Ok(())
}
fn is_maturin_project(pyproject_toml:&toml_edit::DocumentMut)->bool{
let build_system = pyproject_toml.get("build-system").unwrap();
let build_backend = build_system.get("build-backend").unwrap();
build_backend.as_str().unwrap() == "maturin"
}
fn get_workspace_members_path(workspace_section:&toml_edit::Item,workspace_root:PathBuf)->Vec<PathBuf>{
let members = workspace_section.get("members").unwrap();
members.as_array().unwrap().iter()
.map(|v|{
if v.as_str().unwrap().ends_with("/*"){
let path = v.as_str().unwrap().trim_end_matches("/*");
let dir = workspace_root.join(path);
dir.read_dir().unwrap().map(|entry| entry.unwrap().path()).collect()
}
else{
vec![workspace_root.join(v.as_str().unwrap())]
}
})
.flatten().collect()
}
fn get_project_name(toml_doc:&toml_edit::DocumentMut)->String{
let project_section = toml_doc.get("package").unwrap();
let name = project_section.get("name").unwrap();
name.as_str().unwrap().to_string()
}
fn get_module_name(toml_doc:&toml_edit::DocumentMut)->String{
let lib_section = toml_doc.get("lib").unwrap();
let name = lib_section.get("name").unwrap();
name.as_str().unwrap().to_string()
}
fn get_rust_src_files(src_root:PathBuf,rust_src_files:&mut Vec<PathBuf>)->Result<(), Box<dyn Error>>{
let entries = std::fs::read_dir(src_root)?;
for entry in entries{
let entry = entry?;
let path = entry.path();
if path.is_file() && path.extension().unwrap() == "rs"{
rust_src_files.push(path);
}
else if path.is_dir(){
get_rust_src_files(path,rust_src_files)?;
}
}
Ok(())
}
#[cfg(test)]
mod tests{
use super::*;
use std::path::PathBuf;
const TEST_PROJECT_ROOT: &str = "tests\\test-project";
const TEST_PROJECT_ROOT_SINGLE: &str = "tests\\test-project\\single_project";
#[test]
fn test_is_maturin_project(){
let maturin_toml = r#"
[build-system]
build-backend = "maturin"
"#;
let non_maturin_toml = r#"
[build-system]
build-backend = "setuptools"
"#;
let toml_doc = maturin_toml.parse::<DocumentMut>().unwrap();
assert!(is_maturin_project(&toml_doc), "maturinプロジェクトとして認識されるべき");
let toml_doc = non_maturin_toml.parse::<DocumentMut>().unwrap();
assert!(!is_maturin_project(&toml_doc), "maturinプロジェクトとして認識されるべきではない");
}
#[test]
fn test_get_workspace_members_path(){
let current_dir = env::current_dir().unwrap();
let project_root = PathBuf::from(current_dir.join(TEST_PROJECT_ROOT));
let pyproject_toml = std::fs::read_to_string(project_root.join("pyproject.toml")).unwrap();
let toml_doc = pyproject_toml.parse::<DocumentMut>().unwrap();
let workspace_section = toml_doc.get("tool").unwrap().get("uv").unwrap().get("workspace").unwrap();
let members = get_workspace_members_path(workspace_section,project_root);
assert_eq!(members.len(), 4);
assert_eq!(members[0], PathBuf::from(current_dir.join(TEST_PROJECT_ROOT).join("libs").join("lib_a")));
assert_eq!(members[1], PathBuf::from(current_dir.join(TEST_PROJECT_ROOT).join("libs").join("lib_b")));
assert_eq!(members[2], PathBuf::from(current_dir.join(TEST_PROJECT_ROOT).join("libs").join("lib_c")));
assert_eq!(members[3], PathBuf::from(current_dir.join(TEST_PROJECT_ROOT).join("single_project")));
}
#[test]
fn test_get_project_info(){
let current_dir = env::current_dir().unwrap();
let project_root = PathBuf::from(current_dir.join(TEST_PROJECT_ROOT));
let mut project_infos = vec![];
get_maturin_project_info(project_root,&mut project_infos).unwrap();
assert_eq!(project_infos.len(), 2);
}
#[test]
fn test_get_rust_src_files(){
let current_dir = env::current_dir().unwrap();
let project_root = PathBuf::from(current_dir.join(TEST_PROJECT_ROOT_SINGLE).join("src"));
let mut rust_src_files = vec![];
get_rust_src_files(project_root,&mut rust_src_files).unwrap();
assert_eq!(rust_src_files.len(), 4);
}
}